Skip to content

Commit 8f34991

Browse files
authored
Unify all precision validation to be consistent with BigDecimal#add (#442)
BigMath methods was aligned with BigMath.log precision handling that raises "precision must be an Integer" for non-integers. This is different from BigDecimal#add handling that converts with to_int. This commit unifies all precision handling to match BigDecimal#add series. In addition, bigdecimal tests in ruby/spec requires non-integer precision to be accepted.
1 parent a831065 commit 8f34991

File tree

4 files changed

+74
-30
lines changed

4 files changed

+74
-30
lines changed

lib/bigdecimal.rb

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,24 @@ def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc:
2727
raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal"
2828
end
2929

30-
def self.validate_prec(prec, method_name, accept_zero: false) # :nodoc:
31-
raise ArgumentError, 'precision must be an Integer' unless Integer === prec
30+
def self.coerce_validate_prec(prec, method_name, accept_zero: false) # :nodoc:
31+
unless Integer === prec
32+
original = prec
33+
# Emulate Integer.try_convert for ruby < 3.1
34+
if prec.respond_to?(:to_int)
35+
prec = prec.to_int
36+
else
37+
raise TypeError, "no implicit conversion of #{original.class} into Integer"
38+
end
39+
raise TypeError, "can't convert #{original.class} to Integer" unless Integer === prec
40+
end
41+
3242
if accept_zero
3343
raise ArgumentError, "Negative precision for #{method_name}" if prec < 0
3444
else
3545
raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0
3646
end
47+
prec
3748
end
3849

3950
def self.infinity_computation_result # :nodoc:
@@ -83,10 +94,10 @@ def **(y)
8394
#
8495
# Also available as the operator **.
8596
#
86-
def power(y, prec = nil)
87-
Internal.validate_prec(prec, :power) if prec
97+
def power(y, prec = 0)
98+
prec = Internal.coerce_validate_prec(prec, :power, accept_zero: true)
8899
x = self
89-
y = Internal.coerce_to_bigdecimal(y, prec || n_significant_digits, :power)
100+
y = Internal.coerce_to_bigdecimal(y, prec.nonzero? || n_significant_digits, :power)
90101

91102
return Internal.nan_computation_result if x.nan? || y.nan?
92103
return BigDecimal(1) if y.zero?
@@ -133,10 +144,10 @@ def power(y, prec = nil)
133144
return BigDecimal(1)
134145
end
135146

136-
prec ||= BigDecimal.limit.nonzero?
147+
prec = BigDecimal.limit if prec.zero?
137148
frac_part = y.frac
138149

139-
if frac_part.zero? && !prec
150+
if frac_part.zero? && prec.zero?
140151
# Infinite precision calculation for `x ** int` and `x.power(int)`
141152
int_part = y.fix.to_i
142153
int_part = -int_part if (neg = int_part < 0)
@@ -156,7 +167,7 @@ def power(y, prec = nil)
156167
return neg ? BigDecimal(1) / ans : ans
157168
end
158169

159-
prec ||= [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig
170+
prec = [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig if prec.zero?
160171

161172
if y < 0
162173
inv = x.power(-y, prec)
@@ -198,7 +209,7 @@ def power(y, prec = nil)
198209
# Result has at least prec significant digits.
199210
#
200211
def sqrt(prec)
201-
Internal.validate_prec(prec, :sqrt, accept_zero: true)
212+
prec = Internal.coerce_validate_prec(prec, :sqrt, accept_zero: true)
202213
return Internal.infinity_computation_result if infinite? == 1
203214

204215
raise FloatDomainError, 'sqrt of negative value' if self < 0
@@ -238,7 +249,7 @@ module BigMath
238249
# If +decimal+ is NaN, returns NaN.
239250
#
240251
def self.log(x, prec)
241-
BigDecimal::Internal.validate_prec(prec, :log)
252+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :log)
242253
raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x
243254

244255
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log)
@@ -315,7 +326,7 @@ def self.log(x, prec)
315326
# If +decimal+ is NaN, returns NaN.
316327
#
317328
def self.exp(x, prec)
318-
BigDecimal::Internal.validate_prec(prec, :exp)
329+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp)
319330
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp)
320331
return BigDecimal::Internal.nan_computation_result if x.nan?
321332
return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite?

lib/bigdecimal/math.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ module BigMath
4141
# #=> "0.14142135623730950488016887242097e1"
4242
#
4343
def sqrt(x, prec)
44-
BigDecimal::Internal.validate_prec(prec, :sqrt)
44+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :sqrt)
4545
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sqrt)
4646
x.sqrt(prec)
4747
end
@@ -85,7 +85,7 @@ def sqrt(x, prec)
8585
# #=> "0.70710807985947359435812921837984e0"
8686
#
8787
def sin(x, prec)
88-
BigDecimal::Internal.validate_prec(prec, :sin)
88+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :sin)
8989
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin)
9090
return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan?
9191
n = prec + BigDecimal.double_fig
@@ -122,7 +122,7 @@ def sin(x, prec)
122122
# #=> "-0.99999999999999999999999999999997e0"
123123
#
124124
def cos(x, prec)
125-
BigDecimal::Internal.validate_prec(prec, :cos)
125+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :cos)
126126
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos)
127127
return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan?
128128
sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true)
@@ -144,7 +144,7 @@ def cos(x, prec)
144144
# #=> "0.99999999999999999999999830836025e0"
145145
#
146146
def tan(x, prec)
147-
BigDecimal::Internal.validate_prec(prec, :tan)
147+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :tan)
148148
sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec)
149149
end
150150

@@ -160,7 +160,7 @@ def tan(x, prec)
160160
# #=> "-0.78539816339744830961566084581988e0"
161161
#
162162
def atan(x, prec)
163-
BigDecimal::Internal.validate_prec(prec, :atan)
163+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan)
164164
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan)
165165
return BigDecimal::Internal.nan_computation_result if x.nan?
166166
n = prec + BigDecimal.double_fig
@@ -198,7 +198,7 @@ def atan(x, prec)
198198
# #=> "0.31415926535897932384626433832795e1"
199199
#
200200
def PI(prec)
201-
BigDecimal::Internal.validate_prec(prec, :PI)
201+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :PI)
202202
n = prec + BigDecimal.double_fig
203203
zero = BigDecimal("0")
204204
one = BigDecimal("1")
@@ -243,7 +243,7 @@ def PI(prec)
243243
# #=> "0.27182818284590452353602874713527e1"
244244
#
245245
def E(prec)
246-
BigDecimal::Internal.validate_prec(prec, :E)
246+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :E)
247247
BigMath.exp(1, prec)
248248
end
249249
end

test/bigdecimal/test_bigdecimal.rb

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,6 +1813,10 @@ def test_power_with_Bignum
18131813
def test_power_with_intger_infinite_precision
18141814
assert_equal(1234 ** 100, (BigDecimal("12.34") ** 100) * BigDecimal("1e200"))
18151815
assert_in_delta(1234 ** 100, 1 / (BigDecimal("12.34") ** -100) * BigDecimal("1e200"), 1)
1816+
BigDecimal.save_limit do
1817+
BigDecimal.limit 10
1818+
assert_equal(BigDecimal("0.1353679867e110"), BigDecimal("12.34") ** 100)
1819+
end
18161820
end
18171821

18181822
def test_power_with_BigDecimal
@@ -2354,18 +2358,6 @@ def test_BigMath_log_with_nil
23542358
end
23552359
end
23562360

2357-
def test_BigMath_log_with_non_integer_precision
2358-
assert_raise(ArgumentError) do
2359-
BigMath.log(1, 0.5)
2360-
end
2361-
end
2362-
2363-
def test_BigMath_log_with_nil_precision
2364-
assert_raise(ArgumentError) do
2365-
BigMath.log(1, nil)
2366-
end
2367-
end
2368-
23692361
def test_BigMath_log_with_complex
23702362
assert_raise(Math::DomainError) do
23712363
BigMath.log(Complex(1, 2), 20)

test/bigdecimal/test_bigmath.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,47 @@ def test_e
2929
assert_converge_in_precision {|n| E(n) }
3030
end
3131

32+
def assert_consistent_precision_acceptance(accept_zero: false)
33+
value = yield 5
34+
assert_equal(value, yield(5.9))
35+
36+
obj_with_to_int = Object.new
37+
obj_with_to_int.define_singleton_method(:to_int) { 5 }
38+
assert_equal(value, yield(obj_with_to_int))
39+
40+
wrong_to_int = Object.new
41+
wrong_to_int.define_singleton_method(:to_int) { 5.5 }
42+
assert_raise(TypeError) { yield wrong_to_int }
43+
44+
assert_raise(TypeError) { yield nil }
45+
assert_raise(TypeError) { yield '5' }
46+
assert_raise(ArgumentError) { yield(-1) }
47+
if accept_zero
48+
assert_nothing_raised { yield 0 }
49+
else
50+
assert_raise(ArgumentError) { yield 0 }
51+
end
52+
end
53+
54+
def test_consistent_precision_acceptance
55+
x = BigDecimal('1.23456789')
56+
# Exclude div because div(x, nil) is a special case
57+
assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.add(x, prec) }
58+
assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sub(x, prec) }
59+
assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.mult(x, prec) }
60+
assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.power(x, prec) }
61+
assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sqrt(prec) }
62+
assert_consistent_precision_acceptance {|prec| BigMath.sqrt(x, prec) }
63+
assert_consistent_precision_acceptance {|prec| BigMath.exp(x, prec) }
64+
assert_consistent_precision_acceptance {|prec| BigMath.log(x, prec) }
65+
assert_consistent_precision_acceptance {|prec| BigMath.sin(x, prec) }
66+
assert_consistent_precision_acceptance {|prec| BigMath.cos(x, prec) }
67+
assert_consistent_precision_acceptance {|prec| BigMath.tan(x, prec) }
68+
assert_consistent_precision_acceptance {|prec| BigMath.atan(x, prec) }
69+
assert_consistent_precision_acceptance {|prec| BigMath.E(prec) }
70+
assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) }
71+
end
72+
3273
def test_sqrt
3374
assert_in_delta(2**0.5, sqrt(BigDecimal("2"), N))
3475
assert_equal(10, sqrt(BigDecimal("100"), N))

0 commit comments

Comments
 (0)