diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index b264c8b8..5c431fd2 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -7,6 +7,7 @@ # sqrt(x, prec) # sin (x, prec) # cos (x, prec) +# tan (x, prec) # atan(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) @@ -134,6 +135,36 @@ def cos(x, prec) y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y 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(BigDecimal("0.0"), 4).to_s + # #=> "0.0" + # + # BigMath.tan(BigMath.PI(4) / 4, 4).to_s + # #=> "0.999999999999999999999955588155008544487055622030633191403625599381672572e0" + # + def tan(x, prec) + raise ArgumentError, "Zero or negative precision for tan" if prec <= 0 + return BigDecimal("NaN") if x.infinite? || x.nan? + + x_adjusted, sign = adjust_x_and_detect_sign_of_tangent(x, prec) + + t = guarantee_precision(prec) do |n| + c = cos(x_adjusted, n) + s = sqrt(1 - c**2, n) + s = -s if x_adjusted < 0 + s.div(c, n) + end + + sign >= 0 ? t : -t + end + # call-seq: # atan(decimal, numeric) -> BigDecimal # @@ -231,4 +262,35 @@ def E(prec) raise ArgumentError, "Zero or negative precision for E" if prec <= 0 BigMath.exp(1, prec) end -end + + private + + # Adjusts x to be within [-π, π] and returns the adjusted value and sign + def adjust_x_and_detect_sign_of_tangent(x, prec) + pi = PI(prec) + sign = 1 + + if x.abs > pi + n = (x / pi).round + x = x - n * pi + sign = -1 if n.odd? + end + + [x, sign] + end + + # Performs Ziv's loop to ensure the result has the desired precision + def guarantee_precision(prec) + n = prec + BigDecimal.double_fig + max_iterations = 5 + + max_iterations.times do + result = yield(n) + return result if result.precs[0] >= prec + + n += BigDecimal.double_fig + end + + yield(n) + end +end \ No newline at end of file diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index e3402211..9a5b1ebb 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -80,6 +80,17 @@ def test_cos assert_operator(cos(PI(30) * 2, 30), :<=, 1) end + def test_tan + assert_in_delta(0.0, tan(BigDecimal("0.0"), N)) + assert_in_delta(0.0, tan(PI(N), N)) + assert_in_delta(1.0, tan(PI(N) / 4, N)) + assert_in_delta(sqrt(BigDecimal(3), N), tan(PI(N) / 3, N)) + assert_in_delta(sqrt(BigDecimal(3), 10 * N), tan(PI(10 * N) / 3, 10 * N)) + assert_in_delta(0.0, tan(-PI(N), N)) + assert_in_delta(-1.0, tan(-PI(N) / 4, N)) + assert_in_delta(-sqrt(BigDecimal(3), N), tan(-PI(N) / 3, 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))