Skip to content

Commit f7dd7c1

Browse files
committed
Fix exp log power to raise "Computation results in Infinity/NaN" in EXCEPTION_INFINITY/EXCEPTION_NaN mode
1 parent 2bb5167 commit f7dd7c1

2 files changed

Lines changed: 178 additions & 167 deletions

File tree

lib/bigdecimal.rb

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,36 @@ def power(y, prec = nil)
4646
x = self
4747
y = BigMath._coerce_to_bigdecimal(y, :power)
4848

49-
return BigDecimal::NAN if x.nan? || y.nan?
49+
return BigMath._nan_computation_result if x.nan? || y.nan?
50+
return BigDecimal(1) if y.zero?
5051

5152
if y.infinite?
5253
if x < 0
5354
return BigDecimal(0) if x < -1 && y.negative?
5455
return BigDecimal(0) if x > -1 && y.positive?
5556
raise Math::DomainError, 'Result undefined for negative base raised to infinite power'
5657
elsif x < 1
57-
return y.positive? ? BigDecimal(0) : BigDecimal::INFINITY
58+
return y.positive? ? BigDecimal(0) : BigMath._infinity_computation_result
5859
elsif x == 1
5960
return BigDecimal(1)
6061
else
61-
return y.positive? ? BigDecimal::INFINITY : BigDecimal(0)
62+
return y.positive? ? BigMath._infinity_computation_result : BigDecimal(0)
6263
end
6364
end
6465

66+
if x.infinite? && y < 0
67+
# Computation result will be +0 or -0. Avoid overflow.
68+
neg = x < 0 && y.frac.zero? && y % 2 == 1
69+
return neg ? -BigDecimal(0) : BigDecimal(0)
70+
end
71+
6572
if x.zero?
6673
return BigDecimal(1) if y.zero?
6774
return BigDecimal(0) if y > 0
6875
if y.frac.zero? && y % 2 == 1 && x.sign == -1
69-
return -BigDecimal::INFINITY
76+
return -BigMath._infinity_computation_result
7077
else
71-
return BigDecimal::INFINITY
78+
return BigMath._infinity_computation_result
7279
end
7380
elsif x < 0
7481
if y.frac.zero?
@@ -101,7 +108,7 @@ def power(y, prec = nil)
101108
xn *= xn
102109
# Detect overflow/underflow before consuming infinite memory
103110
if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF
104-
return ((xn.exponent > 0) ^ neg ? BigDecimal::INFINITY : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1)
111+
return ((xn.exponent > 0) ^ neg ? BigMath._infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1)
105112
end
106113
end
107114
return neg ? BigDecimal(1) / ans : ans
@@ -112,7 +119,7 @@ def power(y, prec = nil)
112119
if y < 0
113120
inv = x.power(-y, prec)
114121
return BigDecimal(0) if inv.infinite?
115-
return BigDecimal::INFINITY if inv.zero?
122+
return BigMath._infinity_computation_result if inv.zero?
116123
return BigDecimal(1).div(inv, prec)
117124
end
118125

@@ -138,7 +145,7 @@ def power(y, prec = nil)
138145
# Core BigMath methods for BigDecimal (log, exp) are defined here.
139146
# Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'.
140147
module BigMath
141-
def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false)
148+
def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) # :nodoc:
142149
case x
143150
when BigDecimal
144151
return x
@@ -152,11 +159,25 @@ def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false)
152159
raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal"
153160
end
154161

155-
def self._validate_prec(prec, method_name)
162+
def self._validate_prec(prec, method_name) # :nodoc:
156163
raise ArgumentError, 'precision must be an Integer' unless Integer === prec
157164
raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0
158165
end
159166

167+
def self._infinity_computation_result # :nodoc:
168+
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY)
169+
raise FloatDomainError, "Computation results in 'Infinity'"
170+
end
171+
BigDecimal::INFINITY
172+
end
173+
174+
def self._nan_computation_result # :nodoc:
175+
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN)
176+
raise FloatDomainError, "Computation results to 'NaN'"
177+
end
178+
BigDecimal::NAN
179+
end
180+
160181
# call-seq:
161182
# BigMath.log(decimal, numeric) -> BigDecimal
162183
#
@@ -172,9 +193,9 @@ def self._validate_prec(prec, method_name)
172193
def self.log(x, prec)
173194
_validate_prec(prec, :log)
174195
x = _coerce_to_bigdecimal(x, :log, true)
175-
return BigDecimal::NAN if x.nan?
196+
return _nan_computation_result if x.nan?
176197
raise Math::DomainError, 'Zero or negative argument for log' if x <= 0
177-
return BigDecimal::INFINITY if x.infinite?
198+
return _infinity_computation_result if x.infinite?
178199
return BigDecimal(0) if x == 1
179200

180201
if x > 10 || x < 0.1
@@ -238,8 +259,8 @@ def self.log(x, prec)
238259
def self.exp(x, prec)
239260
_validate_prec(prec, :exp)
240261
x = _coerce_to_bigdecimal(x, :exp)
241-
return BigDecimal::NAN if x.nan?
242-
return x.positive? ? BigDecimal::INFINITY : BigDecimal(0) if x.infinite?
262+
return _nan_computation_result if x.nan?
263+
return x.positive? ? _infinity_computation_result : BigDecimal(0) if x.infinite?
243264
return BigDecimal(1) if x.zero?
244265
return BigDecimal(1).div(exp(-x, prec), prec) if x < 0
245266

0 commit comments

Comments
 (0)