diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index b264c8b8..2f7f92ae 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -5,14 +5,28 @@ #-- # Contents: # sqrt(x, prec) +# cbrt(x, prec) +# hypot(x, y, prec) # sin (x, prec) # cos (x, prec) +# tan (x, prec) +# asin(x, prec) +# acos(x, prec) # atan(x, prec) +# atan2(y, x, prec) +# sinh (x, prec) +# cosh (x, prec) +# tanh (x, prec) +# asinh(x, prec) +# acosh(x, prec) +# atanh(x, prec) +# log2 (x, prec) +# log10(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) # # where: -# x ... BigDecimal number to be computed. +# x, y ... BigDecimal number to be computed. # prec ... Number of digits to be obtained. #++ # @@ -40,9 +54,54 @@ module BigMath # #=> "0.1414213562373095048801688724e1" # def sqrt(x, prec) + x = BigMath._coerce_to_bigdecimal(x, :sqrt) x.sqrt(prec) end + # call-seq: + # cbrt(decimal, numeric) -> BigDecimal + # + # Computes the cube root of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # BigMath.cbrt(BigDecimal('2'), 16).to_s + # #=> "0.125992104989487316476721060727822e1" + # + def cbrt(x, prec) + BigMath._validate_prec(prec, :cbrt) + x = BigMath._coerce_to_bigdecimal(x, :cbrt) + return x if x.zero? || x.infinite? || x.nan? + return -cbrt(-x, prec) if x < 0 + + ex = x.exponent / 3 + x *= BigDecimal("1e#{-ex * 3}") + y = BigDecimal(Math.cbrt(x.to_f)) + precs = [prec + BigDecimal.double_fig] + precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig + precs.reverse_each do |p| + y = (2 * y + x.div(y, p).div(y, p)).div(3, p) + end + y * BigDecimal("1e#{ex}") + end + + # call-seq: + # hypot(x, y, numeric) -> BigDecimal + # + # Returns sqrt(x**2 + y**2) to the specified number of digits of + # precision, +numeric+. + # + # BigMath.hypot(BigDecimal('1'), BigDecimal('2'), 16).to_s + # #=> "0.2236067977499789696409173668333333334e1" + # + def hypot(x, y, prec) + BigMath._validate_prec(prec, :hypot) + x = BigMath._coerce_to_bigdecimal(x, :hypot) + return BigDecimal::NAN if x.nan? || y.nan? + return BigDecimal::INFINITY if x.infinite? || y.infinite? + prec2 = prec + BigDecimal.double_fig + sqrt(x.mult(x, prec2) + y.mult(y, prec2), prec) + end + # call-seq: # sin(decimal, numeric) -> BigDecimal # @@ -55,7 +114,8 @@ def sqrt(x, prec) # #=> "0.70710678118654752440082036563292800375e0" # def sin(x, prec) - raise ArgumentError, "Zero or negative precision for sin" if prec <= 0 + BigMath._validate_prec(prec, :sin) + x = BigMath._coerce_to_bigdecimal(x, :sin) return BigDecimal("NaN") if x.infinite? || x.nan? n = prec + BigDecimal.double_fig one = BigDecimal("1") @@ -101,7 +161,8 @@ def sin(x, prec) # #=> "-0.999999999999999999999999999999856613163740061349e0" # def cos(x, prec) - raise ArgumentError, "Zero or negative precision for cos" if prec <= 0 + BigMath._validate_prec(prec, :cos) + x = BigMath._coerce_to_bigdecimal(x, :cos) return BigDecimal("NaN") if x.infinite? || x.nan? n = prec + BigDecimal.double_fig one = BigDecimal("1") @@ -134,6 +195,96 @@ def cos(x, prec) y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y end + # Calculate a relative precision value from a block that calculates with a fixed precision. + private def _ensure_relative_precision(prec) + fixed_point_precision = prec + BigDecimal.double_fig + loop do + value = yield fixed_point_precision + return value if !value.zero? && prec - value.exponent <= fixed_point_precision + + if value.zero? || fixed_point_precision < -value.exponent + # Multiply precision by 3/2 if the calculated precision is not enough for estimating required precision + fixed_point_precision = fixed_point_precision * 3 / 2 + else + fixed_point_precision = prec - value.exponent + BigDecimal.double_fig + end + end + end + + # call-seq: + # tan(decimal, numeric) -> BigDecimal + # + # Computes the tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is Infinity or NaN, returns NaN. + # + # BigMath.tan(BigMath.PI(16) / 3, 16).to_s + # #=> "0.17320508075688772935274463415059e1" + # + def tan(x, prec) + BigMath._validate_prec(prec, :tan) + x = BigMath._coerce_to_bigdecimal(x, :tan) + return BigDecimal(0) if x.zero? + + # BigMath calculates sin with relative precision only when x.abs is small + sin = x.abs < 3 ? sin(x, prec) : _ensure_relative_precision(prec) {|p| sin(x, p) } + + cos = _ensure_relative_precision(prec) {|p| cos(x, p) } + sin.div(cos, prec + BigDecimal.double_fig) + end + + # call-seq: + # asin(decimal, numeric) -> BigDecimal + # + # Computes the arcsine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.asin(BigDecimal('0.5'), 16).to_s + # #=> "0.52359877559829887307710723054659e0" + # + def asin(x, prec) + BigMath._validate_prec(prec, :asin) + x = BigMath._coerce_to_bigdecimal(x, :asin) + raise Math::DomainError, "Out of domain argument for asin" if x < -1 || x > 1 + return BigDecimal::NAN if x.nan? + prec2 = prec + BigDecimal.double_fig + cos = (1 - x**2).sqrt(prec2) + if cos.zero? + pi = PI(prec) + x > 0 ? pi / 2 : -pi / 2 + else + atan(x.div(cos, prec2), prec) + end + end + + # call-seq: + # acos(decimal, numeric) -> BigDecimal + # + # Computes the arccosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.acos(BigDecimal('0.5'), 16).to_s + # #=> "0.10471975511965977461542144610932e1" + # + def acos(x, prec) + BigMath._validate_prec(prec, :acos) + x = BigMath._coerce_to_bigdecimal(x, :acos) + raise Math::DomainError, "Out of domain argument for acos" if x < -1 || x > 1 + + return PI(prec) / 2 - asin(x, prec) if x < 0 + return PI(prec) / 2 if x.zero? + return BigDecimal::NAN if x.nan? + + prec2 = prec + BigDecimal.double_fig + sin = (1 - x**2).sqrt(prec2) + atan(sin.div(x, prec2), prec) + end + # call-seq: # atan(decimal, numeric) -> BigDecimal # @@ -146,7 +297,8 @@ def cos(x, prec) # #=> "-0.785398163397448309615660845819878471907514682065e0" # def atan(x, prec) - raise ArgumentError, "Zero or negative precision for atan" if prec <= 0 + BigMath._validate_prec(prec, :atan) + x = BigMath._coerce_to_bigdecimal(x, :atan) return BigDecimal("NaN") if x.nan? pi = PI(prec) x = -x if neg = x < 0 @@ -170,7 +322,226 @@ def atan(x, prec) y *= 2 if dbl y = pi / 2 - y if inv y = -y if neg - y + y.mult(1, n) + end + + # call-seq: + # atan2(decimal, decimal, numeric) -> BigDecimal + # + # Computes the arctangent of y and x to the specified number of digits of + # precision, +numeric+. + # + # BigMath.atan2(BigDecimal('-1'), BigDecimal('1'), 16).to_s + # #=> "-0.785398163397448309615660845819878471907514682065e0" + # + def atan2(y, x, prec) + BigMath._validate_prec(prec, :atan2) + x = BigMath._coerce_to_bigdecimal(x, :atan2) + y = BigMath._coerce_to_bigdecimal(y, :atan2) + if x.infinite? || y.infinite? + one = BigDecimal(1) + zero = BigDecimal(0) + x = x.infinite? ? (x > 0 ? one : -one) : zero + y = y.infinite? ? (y > 0 ? one : -one) : y.sign * zero + end + + return x.sign >= 0 ? BigDecimal(0) : y.sign * PI(prec) if y.zero? + + y = -y if neg = y < 0 + xlarge = y.abs < x.abs + divprec = prec + BigDecimal.double_fig + if x > 0 + v = xlarge ? atan(y.div(x, divprec), prec) : PI(prec) / 2 - atan(x.div(y, divprec), prec) + else + v = xlarge ? PI(prec) - atan(-y.div(x, divprec), prec) : PI(prec) / 2 + atan(x.div(-y, divprec), prec) + end + neg ? -v : v + end + + # call-seq: + # sinh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic sine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.sinh(BigDecimal('1'), 16).to_s + # #=> "0.11752011936438014568823818505956e1" + # + def sinh(x, prec) + BigMath._validate_prec(prec, :sinh) + x = BigMath._coerce_to_bigdecimal(x, :sinh) + return BigDecimal::NAN if x.nan? + return x if x.infinite? + + prec += BigDecimal.double_fig + prec -= x.exponent if x.exponent < 0 + e = BigMath.exp(x, prec) + (e - BigDecimal(1).div(e, prec)).div(2, prec) + end + + # call-seq: + # cosh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic cosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.cosh(BigDecimal('1'), 16).to_s + # #=> "0.15430806348152437784779056207571e1" + # + def cosh(x, prec) + BigMath._validate_prec(prec, :cosh) + x = BigMath._coerce_to_bigdecimal(x, :cosh) + return BigDecimal::NAN if x.nan? + return BigDecimal::INFINITY if x.infinite? + + prec += BigDecimal.double_fig + e = BigMath.exp(x, prec) + (e + BigDecimal(1).div(e, prec)).div(2, prec) + end + + # call-seq: + # tanh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.tanh(BigDecimal('1'), 16).to_s + # #=> "0.7615941559557648881194582826048e0" + # + def tanh(x, prec) + BigMath._validate_prec(prec, :tanh) + x = BigMath._coerce_to_bigdecimal(x, :tanh) + return BigDecimal::NAN if x.nan? + return BigDecimal(x.infinite?) if x.infinite? + + prec += BigDecimal.double_fig + prec2 = prec + [-x.exponent, 0].max + e = BigMath.exp(x, prec2) + einv = BigDecimal(1).div(e, prec2) + (e - einv).div(e + einv, prec) + end + + # call-seq: + # asinh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic sine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.asinh(BigDecimal('1'), 16).to_s + # #=> "0.881373587019543025232609324892919887466177636058e0" + # + def asinh(x, prec) + BigMath._validate_prec(prec, :asinh) + x = BigMath._coerce_to_bigdecimal(x, :asinh) + return x if x.nan? || x.infinite? + return -asinh(-x, prec) if x < 0 + + sqrt_prec = prec + [-x.exponent, 0].max + BigMath.log(x + sqrt(x**2 + 1, sqrt_prec), prec) + end + + # call-seq: + # acosh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic cosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.acosh(BigDecimal('2'), 16).to_s + # #=> "0.1316957896924816708625046347239934461496535769096e1" + # + def acosh(x, prec) + BigMath._validate_prec(prec, :acosh) + x = BigMath._coerce_to_bigdecimal(x, :acosh) + raise Math::DomainError, "Out of domain argument for acosh" if x < 1 + return BigDecimal::INFINITY if x.infinite? + return BigDecimal::NAN if x.nan? + + BigMath.log(x + sqrt(x**2 - 1, prec), prec) + end + + # call-seq: + # atanh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.atanh(BigDecimal('0.5'), 16).to_s + # #=> "0.54930614433405484569762261846126e0" + # + def atanh(x, prec) + BigMath._validate_prec(prec, :atanh) + x = BigMath._coerce_to_bigdecimal(x, :atanh) + raise Math::DomainError, "Out of domain argument for atanh" if x < -1 || x > 1 + return BigDecimal::NAN if x.nan? + return BigDecimal::INFINITY if x == 1 + return -BigDecimal::INFINITY if x == -1 + + prec += BigDecimal.double_fig + (BigMath.log(x + 1, prec) - BigMath.log(1 - x, prec)).div(2, prec) + end + + # call-seq: + # BigMath.log2(decimal, numeric) -> BigDecimal + # + # Computes the base 2 logarithm of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # If +decimal+ is zero or negative, raises Math::DomainError. + # + # If +decimal+ is positive infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.log2(BigDecimal('3'), 16).to_s + # #=> "0.158496250072115618145373894394782e1" + # + def log2(x, prec) + BigMath._validate_prec(prec, :log2) + x = BigMath._coerce_to_bigdecimal(x, :log2, true) + return BigDecimal::NAN if x.nan? + return BigDecimal::INFINITY if x.infinite? == 1 + + prec2 = prec + BigDecimal.double_fig * 3 / 2 + v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(2), prec2), prec2) + v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) + end + + # call-seq: + # BigMath.log10(decimal, numeric) -> BigDecimal + # + # Computes the base 10 logarithm of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # If +decimal+ is zero or negative, raises Math::DomainError. + # + # If +decimal+ is positive infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.log10(BigDecimal('3'), 16).to_s + # #=> "0.47712125471966243729502790325512e0" + # + def log10(x, prec) + BigMath._validate_prec(prec, :log10) + x = BigMath._coerce_to_bigdecimal(x, :log10, true) + return BigDecimal::NAN if x.nan? + return BigDecimal::INFINITY if x.infinite? == 1 + + prec2 = prec + BigDecimal.double_fig * 3 / 2 + v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(10), prec2), prec2) + v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) end # call-seq: @@ -183,7 +554,7 @@ def atan(x, prec) # #=> "0.3141592653589793238462643388813853786957412e1" # def PI(prec) - raise ArgumentError, "Zero or negative precision for PI" if prec <= 0 + BigMath._validate_prec(prec, :PI) n = prec + BigDecimal.double_fig zero = BigDecimal("0") one = BigDecimal("1") @@ -228,7 +599,7 @@ def PI(prec) # #=> "0.271828182845904523536028752390026306410273e1" # def E(prec) - raise ArgumentError, "Zero or negative precision for E" if prec <= 0 + BigMath._validate_prec(prec, :E) BigMath.exp(1, prec) end end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index e4eb570f..7226a3ac 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -9,6 +9,7 @@ class TestBigMath < Test::Unit::TestCase # SQRT in 116 (= 100 + double_fig) digits SQRT2 = BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309123") SQRT3 = BigDecimal("1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756756261414154067") + SQRT5 = BigDecimal("2.2360679774997896964091736687312762354406183596115257242708972454105209256378048994144144083787822749695081761507738") PINF = BigDecimal("+Infinity") MINF = BigDecimal("-Infinity") NAN = BigDecimal("NaN") @@ -33,6 +34,37 @@ def test_sqrt assert_relative_precision {|n| sqrt(BigDecimal("2e50"), n) } end + def test_cbrt + assert_equal(1234, cbrt(BigDecimal(1234**3), N)) + assert_equal(-12345, cbrt(BigDecimal(-12345**3), N)) + assert_equal(12345678987654321, cbrt(BigDecimal(12345678987654321) ** 3, N)) + assert_equal(0, cbrt(BigDecimal("0"), N)) + assert_equal(0, cbrt(BigDecimal("-0"), N)) + assert_equal(PINF, cbrt(PINF, N)) + assert_equal(MINF, cbrt(MINF, N)) + + assert_in_delta(SQRT2, cbrt(SQRT2 ** 3, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT3, cbrt(SQRT3 ** 3, 100), BigDecimal("1e-100")) + assert_equal(BigDecimal("3e50"), cbrt(BigDecimal("27e150"), N)) + assert_equal(BigDecimal("-4e50"), cbrt(BigDecimal("-64e150"), N)) + assert_in_epsilon(Math.cbrt(28e150), cbrt(BigDecimal("28e150"), N)) + assert_in_epsilon(Math.cbrt(27e151), cbrt(BigDecimal("27e151"), N)) + assert_relative_precision {|n| cbrt(BigDecimal("2"), n) } + assert_relative_precision {|n| cbrt(BigDecimal("2e-50"), n) } + assert_relative_precision {|n| cbrt(BigDecimal("2e50"), n) } + end + + def test_hypot + assert_in_delta(SQRT2, hypot(BigDecimal("1"), BigDecimal("1"), 100), BigDecimal("1e-100")) + assert_in_delta(SQRT5, hypot(SQRT2, SQRT3, 100), BigDecimal("1e-100")) + assert_equal(0, hypot(BigDecimal(0), BigDecimal(0), N)) + assert_equal(PINF, hypot(PINF, SQRT3, N)) + assert_equal(PINF, hypot(SQRT3, MINF, N)) + assert_relative_precision {|n| hypot(BigDecimal("1e-30"), BigDecimal("2e-30"), n) } + assert_relative_precision {|n| hypot(BigDecimal("1.23"), BigDecimal("4.56"), n) } + assert_relative_precision {|n| hypot(BigDecimal("2e30"), BigDecimal("1e30"), n) } + end + def test_sin assert_in_delta(0.0, sin(BigDecimal("0.0"), N)) assert_in_delta(Math.sqrt(2.0) / 2, sin(PI(N) / 4, N)) @@ -80,6 +112,61 @@ def test_cos assert_operator(cos(PI(30) * 2, 30), :<=, 1) end + def test_tan + assert_in_delta(0.0, tan(-PI(N), N)) + assert_in_delta(0.0, tan(BigDecimal(0), N)) + assert_in_delta(0.0, tan(PI(N), N)) + assert_in_delta(1.0, tan(PI(N) / 4, N)) + assert_in_delta(-1.0, tan(-PI(N) / 4, N)) + assert_in_delta(-1.0, tan(PI(N) * 3 / 4, N)) + assert_in_delta(1.0, tan(-PI(N) * 3 / 4, N)) + assert_in_delta(0.0, tan(PI(N) * 100, N)) + assert_in_delta(1.0, tan(PI(N) * 101 / 4, N)) + assert_in_delta(-1.0, tan(PI(N) * 103 / 4, N)) + assert_in_delta(BigDecimal("1").div(SQRT3, 100), tan(PI(100) / 6, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT3, tan(PI(100) / 3, 100), BigDecimal("1e-100")) + assert_relative_precision {|n| tan(BigDecimal("0.5"), n) } + assert_relative_precision {|n| tan(BigDecimal("1e-30"), n) } + assert_relative_precision {|n| tan(BigDecimal("1.5"), n) } + assert_relative_precision {|n| tan(PI(100) / 2, n) } + assert_relative_precision {|n| tan(PI(200) * 101 / 2, n) } + assert_relative_precision {|n| tan(PI(100), n) } + assert_relative_precision {|n| tan(PI(200) * 100, n) } + end + + def test_asin + ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| + assert_in_delta(Math.asin(x.to_f), asin(BigDecimal(x), N)) + end + assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("1.1"), N) } + assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("-1.1"), N) } + assert_in_delta(PI(100) / 6, asin(BigDecimal("0.5"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| asin(BigDecimal("-0.4"), n) } + assert_relative_precision {|n| asin(BigDecimal("0.3"), n) } + assert_relative_precision {|n| asin(BigDecimal("0.9"), n) } + assert_relative_precision {|n| asin(BigDecimal("0.#{"9" * 50}"), n) } + assert_relative_precision {|n| asin(BigDecimal("0.#{"9" * 100}"), n) } + assert_relative_precision {|n| asin(BigDecimal("0.#{"9" * 195}"), n) } + assert_relative_precision {|n| asin(BigDecimal("1e-30"), n) } + end + + def test_acos + ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| + assert_in_delta(Math.acos(x.to_f), acos(BigDecimal(x), N)) + end + assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("1.1"), N) } + assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("-1.1"), N) } + assert_equal(0, acos(BigDecimal("1.0"), N)) + assert_in_delta(PI(100) / 3, acos(BigDecimal("0.5"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| acos(BigDecimal("-0.4"), n) } + assert_relative_precision {|n| acos(BigDecimal("0.3"), n) } + assert_relative_precision {|n| acos(BigDecimal("0.9"), n) } + assert_relative_precision {|n| acos(BigDecimal("0.#{"9" * 50}"), n) } + assert_relative_precision {|n| acos(BigDecimal("0.#{"9" * 100}"), n) } + assert_relative_precision {|n| acos(BigDecimal("0.#{"9" * 195}"), n) } + assert_relative_precision {|n| acos(BigDecimal("1e-30"), n) } + end + def test_atan assert_equal(0.0, atan(BigDecimal("0.0"), N)) assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) @@ -93,6 +180,98 @@ def test_atan assert_relative_precision {|n| atan(BigDecimal("1e30"), n)} end + def test_atan2 + zero = BigDecimal(0) + one = BigDecimal(1) + assert_equal(0, atan2(zero, zero, N)) + assert_equal(0, atan2(zero, one, N)) + [MINF, -one, -zero, zero, one, PINF].repeated_permutation(2) do |y, x| + assert_in_delta(Math::atan2(y.to_f, x.to_f), atan2(y, x, N)) + end + assert_in_delta(PI(100), atan2(zero, -one, 100), BigDecimal("1e-100")) + assert_in_delta(PI(100) / 2, atan2(one, zero, 100), BigDecimal("1e-100")) + assert_in_delta(-PI(100) / 2, atan2(-one, zero, 100), BigDecimal("1e-100")) + assert_in_delta(PI(100) / 3, atan2(BigDecimal(3), SQRT3, 100), BigDecimal("1e-100")) + assert_in_delta(PI(100) / 6, atan2(SQRT3, BigDecimal(3), 100), BigDecimal("1e-100")) + ['-1e20', '-2', '-1e-30', '1e-30', '2', '1e20'].repeated_permutation(2) do |y, x| + assert_in_delta(Math.atan2(y.to_f, x.to_f), atan2(BigDecimal(y), BigDecimal(x), N)) + assert_relative_precision {|n| atan2(BigDecimal(y), BigDecimal(x), n) } + end + end + + def test_hyperbolic + [-1, 0, 0.5, 1, 10].each do |x| + assert_in_delta(Math.sinh(x), sinh(BigDecimal(x.to_s), N)) + assert_in_delta(Math.cosh(x), cosh(BigDecimal(x.to_s), N)) + assert_in_delta(Math.tanh(x), tanh(BigDecimal(x.to_s), N)) + end + [MINF, BigDecimal(0), PINF].each do |x| + assert_equal(Math.sinh(x.to_f), sinh(x, N).to_f) + assert_equal(Math.cosh(x.to_f), cosh(x, N).to_f) + assert_equal(Math.tanh(x.to_f), tanh(x, N).to_f) + end + + x = BigDecimal("0.3") + assert_in_delta(tanh(x, 100), sinh(x, 100) / cosh(x, 100), BigDecimal("1e-100")) + + e = E(116) + assert_in_delta((e - 1 / e) / 2, sinh(BigDecimal(1), 100), BigDecimal("1e-100")) + assert_in_delta((e + 1 / e) / 2, cosh(BigDecimal(1), 100), BigDecimal("1e-100")) + assert_in_delta((e - 1 / e) / (e + 1 / e), tanh(BigDecimal(1), 100), BigDecimal("1e-100")) + + ["1e-30", "0.2", "10", "100"].each do |x| + assert_relative_precision {|n| sinh(BigDecimal(x), n)} + assert_relative_precision {|n| cosh(BigDecimal(x), n)} + assert_relative_precision {|n| tanh(BigDecimal(x), n)} + end + end + + def test_asinh + [-3, 0.5, 10].each do |x| + assert_in_delta(Math.asinh(x), asinh(BigDecimal(x.to_s), N)) + end + assert_equal(0, asinh(BigDecimal(0), N)) + assert_equal(PINF, asinh(PINF, N)) + assert_equal(MINF, asinh(MINF, N)) + + x = BigDecimal(1) / 7 + assert_in_delta(x, sinh(asinh(x, 100), 100), BigDecimal("1e-100")) + + ["1e-30", "0.2", "10", "100"].each do |x| + assert_relative_precision {|n| asinh(BigDecimal(x), n)} + end + end + + def test_acosh + [1.5, 2, 10].each do |x| + assert_in_delta(Math.acosh(x), acosh(BigDecimal(x.to_s), N)) + end + assert_equal(0, acosh(BigDecimal(1), N)) + assert_equal(PINF, acosh(PINF, N)) + + x = BigDecimal(8) / 7 + assert_in_delta(x, cosh(acosh(x, 100), 100), BigDecimal("1e-100")) + + ["1." + "0" * 30 + "1", "1.5", "2", "100"].each do |x| + assert_relative_precision {|n| acosh(BigDecimal(x), n)} + end + end + + def test_atanh + [-0.5, 0.1, 0.9].each do |x| + assert_in_delta(Math.atanh(x), atanh(BigDecimal(x.to_s), N)) + end + assert_equal(0, atanh(BigDecimal(0), N)) + assert_equal(PINF, atanh(BigDecimal(1), N)) + + x = BigDecimal(1) / 7 + assert_in_delta(x, tanh(atanh(x, 100), 100), BigDecimal("1e-100")) + + ["1e-30", "0.5", "0.9" + "9" * 30].each do |x| + assert_relative_precision {|n| atanh(BigDecimal(x), n)} + end + end + def test_exp [-100, -2, 0.5, 10, 100].each do |x| assert_in_epsilon(Math.exp(x), BigMath.exp(BigDecimal(x, 0), N)) @@ -130,4 +309,40 @@ def test_log end SRC end + + def test_log2 + assert_raise(Math::DomainError) { log2(BigDecimal("0"), N) } + assert_raise(Math::DomainError) { log2(BigDecimal("-1"), N) } + assert_raise(Math::DomainError) { log2(MINF, N) } + assert_equal(PINF, log2(PINF, N)) + assert_in_epsilon(BigDecimal("1.5849625007211561814537389439478165087598144076924810604557526545410982277943585625222804749180882420909806624750592"), + log2(BigDecimal("3"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| log2(BigDecimal("3"), n) } + assert_relative_precision {|n| log2(BigDecimal("3e20"), n) } + assert_relative_precision {|n| log2(BigDecimal("1e-20") + 1, n) } + [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) + [0, 1, 2, 11, 123].each do |n| + assert_equal(n, log2(BigDecimal(2**n), N)) + end + end + end + + def test_log10 + assert_raise(Math::DomainError) { log10(BigDecimal("0"), N) } + assert_raise(Math::DomainError) { log10(BigDecimal("-1"), N) } + assert_raise(Math::DomainError) { log10(MINF, N) } + assert_equal(PINF, log10(PINF, N)) + assert_in_epsilon(BigDecimal("0.4771212547196624372950279032551153092001288641906958648298656403052291527836611230429683556476163015104646927682520"), + log10(BigDecimal("3"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| log10(BigDecimal("3"), n) } + assert_relative_precision {|n| log10(BigDecimal("3e20"), n) } + assert_relative_precision {|n| log10(BigDecimal("1e-20") + 1, n) } + [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) + [0, 1, 2, 11, 123].each do |n| + assert_equal(n, log10(BigDecimal(10**n), N)) + end + end + end end