Skip to content

Commit 6f61b71

Browse files
committed
Implement BigMath.tan
Calculate `tan(x, prec)` by `relative_precision_sin/relative_precision_cos`.
1 parent bf22f51 commit 6f61b71

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

lib/bigdecimal/math.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# sqrt(x, prec)
88
# sin (x, prec)
99
# cos (x, prec)
10+
# tan (x, prec)
1011
# atan(x, prec)
1112
# PI (prec)
1213
# E (prec) == exp(1.0,prec)
@@ -134,6 +135,43 @@ def cos(x, prec)
134135
y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y
135136
end
136137

138+
# Calculate a relative precision value from a block that calculates with a fixed precision.
139+
private def _ensure_relative_precision(prec)
140+
fixed_point_precision = prec + BigDecimal.double_fig
141+
loop do
142+
value = yield fixed_point_precision
143+
return value if !value.zero? && prec - value.exponent <= fixed_point_precision
144+
145+
if value.zero? || fixed_point_precision < -value.exponent
146+
# Multiply precision by 3/2 if the calculated precision is not enough for estimating required precision
147+
fixed_point_precision = fixed_point_precision * 3 / 2
148+
else
149+
fixed_point_precision = prec - value.exponent + BigDecimal.double_fig
150+
end
151+
end
152+
end
153+
154+
# call-seq:
155+
# tan(decimal, numeric) -> BigDecimal
156+
#
157+
# Computes the tangent of +decimal+ to the specified number of digits of
158+
# precision, +numeric+.
159+
#
160+
# If +decimal+ is Infinity or NaN, returns NaN.
161+
#
162+
# BigMath.tan(BigMath.PI(16) / 3, 16).to_s
163+
# #=> "0.17320508075688772935274463415059e1"
164+
#
165+
def tan(x, prec)
166+
return BigDecimal(0) if x.zero?
167+
168+
# BigMath calculates sin with relative precision only when x.abs is small
169+
sin = x.abs < 3 ? sin(x, prec) : _ensure_relative_precision(prec) {|p| sin(x, p) }
170+
171+
cos = _ensure_relative_precision(prec) {|p| cos(x, p) }
172+
sin.div(cos, prec + BigDecimal.double_fig)
173+
end
174+
137175
# call-seq:
138176
# atan(decimal, numeric) -> BigDecimal
139177
#

test/bigdecimal/test_bigmath.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,28 @@ def test_cos
8080
assert_operator(cos(PI(30) * 2, 30), :<=, 1)
8181
end
8282

83+
def test_tan
84+
assert_in_delta(0.0, tan(-PI(N), N))
85+
assert_in_delta(0.0, tan(BigDecimal(0), N))
86+
assert_in_delta(0.0, tan(PI(N), N))
87+
assert_in_delta(1.0, tan(PI(N) / 4, N))
88+
assert_in_delta(-1.0, tan(-PI(N) / 4, N))
89+
assert_in_delta(-1.0, tan(PI(N) * 3 / 4, N))
90+
assert_in_delta(1.0, tan(-PI(N) * 3 / 4, N))
91+
assert_in_delta(0.0, tan(PI(N) * 100, N))
92+
assert_in_delta(1.0, tan(PI(N) * 101 / 4, N))
93+
assert_in_delta(-1.0, tan(PI(N) * 103 / 4, N))
94+
assert_in_delta(BigDecimal("1").div(SQRT3, 100), tan(PI(100) / 6, 100), BigDecimal("1e-100"))
95+
assert_in_delta(SQRT3, tan(PI(100) / 3, 100), BigDecimal("1e-100"))
96+
assert_relative_precision {|n| tan(BigDecimal("0.5"), n) }
97+
assert_relative_precision {|n| tan(BigDecimal("1e-30"), n) }
98+
assert_relative_precision {|n| tan(BigDecimal("1.5"), n) }
99+
assert_relative_precision {|n| tan(PI(100) / 2, n) }
100+
assert_relative_precision {|n| tan(PI(200) * 101 / 2, n) }
101+
assert_relative_precision {|n| tan(PI(100), n) }
102+
assert_relative_precision {|n| tan(PI(200) * 100, n) }
103+
end
104+
83105
def test_atan
84106
assert_equal(0.0, atan(BigDecimal("0.0"), N))
85107
assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N))

0 commit comments

Comments
 (0)