Skip to content

Commit b468843

Browse files
committed
BigMath.exp overflow/underflow check
`BigMath.exp(BigDecimal('1e100000000'), 20)` was slow. Skip calculation when x.exponent >= 21 which always result in overflow or underflow.
1 parent 72937b7 commit b468843

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

lib/bigdecimal.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ def self.infinity_computation_result # :nodoc:
5757
BigDecimal::INFINITY
5858
end
5959

60+
def self.underflow_computation_result # :nodoc:
61+
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_UNDERFLOW)
62+
raise FloatDomainError, "Computation results in 'Underflow'"
63+
end
64+
BigDecimal(0)
65+
end
66+
6067
def self.nan_computation_result # :nodoc:
6168
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN)
6269
raise FloatDomainError, "Computation results to 'NaN'"
@@ -359,7 +366,17 @@ def exp(x, prec)
359366
prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp)
360367
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp)
361368
return BigDecimal::Internal.nan_computation_result if x.nan?
362-
return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite?
369+
if x.infinite? || x.exponent >= 21 # exp(10**20) and exp(-10**20) overflows/underflows 64-bit exponent
370+
if x.positive?
371+
return BigDecimal::Internal.infinity_computation_result
372+
elsif x.infinite?
373+
# exp(-Infinity) is +0 by definition, this is not an underflow.
374+
return BigDecimal(0)
375+
else
376+
return BigDecimal::Internal.underflow_computation_result
377+
end
378+
end
379+
363380
return BigDecimal(1) if x.zero?
364381

365382
# exp(x * 10**cnt) = exp(x)**(10**cnt)

test/bigdecimal/helper.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def assert_infinite_calculation(positive:)
7171
BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false)
7272
positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield)
7373
BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true)
74-
assert_raise_with_message(FloatDomainError, /Infinity/) { yield }
74+
assert_raise_with_message(FloatDomainError, /infinity|overflow/i) { yield }
7575
end
7676
end
7777

@@ -83,6 +83,15 @@ def assert_negative_infinite_calculation(&block)
8383
assert_infinite_calculation(positive: false, &block)
8484
end
8585

86+
def assert_underflow_calculation
87+
BigDecimal.save_exception_mode do
88+
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)
89+
assert_equal(BigDecimal(0), yield)
90+
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true)
91+
assert_raise_with_message(FloatDomainError, /underflow/i) { yield }
92+
end
93+
end
94+
8695
def assert_nan_calculation(&block)
8796
BigDecimal.save_exception_mode do
8897
BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false)

test/bigdecimal/test_bigdecimal.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,13 +2296,24 @@ def test_exp_with_negative
22962296
end
22972297

22982298
def test_exp_with_negative_infinite
2299+
# exp(-infinity) is exactly zero. This is not an underflow.
22992300
assert_equal(0, BigMath.exp(NEGATIVE_INFINITY, 20))
23002301
end
23012302

23022303
def test_exp_with_positive_infinite
23032304
assert_positive_infinite_calculation { BigMath.exp(BigDecimal::INFINITY, 20) }
23042305
end
23052306

2307+
def test_exp_with_overflow_underflow
2308+
assert_underflow_calculation { BigMath.exp(-1e+100, 20) }
2309+
assert_underflow_calculation { BigMath.exp(-0.9e+20, 20) }
2310+
assert_positive_infinite_calculation { BigMath.exp(1e+100, 20) }
2311+
assert_positive_infinite_calculation { BigMath.exp(0.9e+20, 20) }
2312+
huge = BigDecimal("0.1e#{EXPONENT_MAX / 100}")
2313+
assert_positive_infinite_calculation { BigMath.exp(huge, 20) }
2314+
assert_underflow_calculation { BigMath.exp(-huge, 20) }
2315+
end
2316+
23062317
def test_exp_with_nan
23072318
assert_nan_calculation { BigMath.exp(BigDecimal::NAN, 20) }
23082319
end

0 commit comments

Comments
 (0)