From 2f4307c3baa5832afb1f9614c0bd1db5ac163bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Tue, 28 Jun 2022 22:35:58 +0200 Subject: [PATCH 1/5] Add support for tangent function --- lib/bigdecimal/math.rb | 19 +++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 0b9d0648..bff26226 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) Note: |x|<1, x=0.9999 may not converge. # PI (prec) # E (prec) == exp(1.0,prec) @@ -132,6 +133,24 @@ def cos(x, prec) 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) + sin(x, prec) / cos(x, prec) + end + # call-seq: # atan(decimal, numeric) -> BigDecimal # diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 5bf1fbf3..6d365152 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -53,6 +53,16 @@ def test_cos assert_in_delta(0.0, cos(PI(N) * BigDecimal("301.5"), N)) 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(Math.sqrt(3.0), tan(PI(N) / 3, N)) + assert_in_delta(0.0, tan(-PI(N), N)) + assert_in_delta(-1.0, tan(-PI(N) / 4, N)) + assert_in_delta(-Math.sqrt(3.0), 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)) From 7682fc7343b6176539a475d9a0bdad9204263078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Thu, 28 Jul 2022 09:52:29 +0200 Subject: [PATCH 2/5] Apply review comments for tests --- test/bigdecimal/test_bigmath.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 6d365152..2673f7c1 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -57,10 +57,11 @@ 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(Math.sqrt(3.0), tan(PI(N) / 3, 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(-Math.sqrt(3.0), tan(-PI(N) / 3, N)) + assert_in_delta(-sqrt(BigDecimal(3), N), tan(-PI(N) / 3, N)) end def test_atan From 8785e7a827a9302ed5dd08d245e8f3ce0c72247d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Sun, 30 Oct 2022 14:42:25 +0100 Subject: [PATCH 3/5] Make tan depend only on sin --- lib/bigdecimal/math.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index bff26226..baae69cb 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -148,7 +148,9 @@ def cos(x, prec) # #=> "0.999999999999999999999955588155008544487055622030633191403625599381672572e0" # def tan(x, prec) - sin(x, prec) / cos(x, prec) + sine = sin(x, prec) + cosine = sqrt(1 - sine**2, prec) + sine / cosine end # call-seq: From 945ac06424122320724e0fc2e832698b85deb39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Thu, 19 Jun 2025 21:35:53 +0200 Subject: [PATCH 4/5] Use Ziv's loop method suggested in review --- lib/bigdecimal/math.rb | 48 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index baae69cb..c3045df0 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -148,9 +148,18 @@ def cos(x, prec) # #=> "0.999999999999999999999955588155008544487055622030633191403625599381672572e0" # def tan(x, prec) - sine = sin(x, prec) - cosine = sqrt(1 - sine**2, prec) - sine / cosine + raise ArgumentError, "Zero or negative precision for tan" if prec <= 0 + return BigDecimal("NaN") if x.infinite? || x.nan? + + x, sign = adjust_x_and_detect_sign_of_tangent(x, prec) + + t = guarantee_precision(prec) do |n| + c = cos(x, n) + s = sqrt(1 - c**2, n) + s.div(c, n) + end + + sign >= 0 ? t : -t end # call-seq: @@ -250,4 +259,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 From d0d8bba782c1b2785787b19b5368f365a6363af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Thu, 19 Jun 2025 21:40:10 +0200 Subject: [PATCH 5/5] Test failing test --- lib/bigdecimal/math.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 0da99bf5..5c431fd2 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -153,11 +153,12 @@ def tan(x, prec) raise ArgumentError, "Zero or negative precision for tan" if prec <= 0 return BigDecimal("NaN") if x.infinite? || x.nan? - x, sign = adjust_x_and_detect_sign_of_tangent(x, prec) + x_adjusted, sign = adjust_x_and_detect_sign_of_tangent(x, prec) t = guarantee_precision(prec) do |n| - c = cos(x, n) + c = cos(x_adjusted, n) s = sqrt(1 - c**2, n) + s = -s if x_adjusted < 0 s.div(c, n) end