1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
|
if RUBY_ENGINE == 'jruby'
JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary")
class BigDecimal
def _decimal_shift(i) # :nodoc:
to_java.move_point_right(i).to_d
end
end
else
require 'bigdecimal.so'
end
class BigDecimal
module Internal # :nodoc:
# Default extra precision for intermediate calculations
# This value is currently the same as BigDecimal.double_fig, but defined separately for future changes.
EXTRA_PREC = 16
# Coerce x to BigDecimal with the specified precision.
# TODO: some methods (example: BigMath.exp) require more precision than specified to coerce.
def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc:
case x
when BigDecimal
return x
when Integer, Float
return BigDecimal(x, 0)
when Rational
return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max)
end
raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal"
end
def self.coerce_validate_prec(prec, method_name, accept_zero: false) # :nodoc:
unless Integer === prec
original = prec
# Emulate Integer.try_convert for ruby < 3.1
if prec.respond_to?(:to_int)
prec = prec.to_int
else
raise TypeError, "no implicit conversion of #{original.class} into Integer"
end
raise TypeError, "can't convert #{original.class} to Integer" unless Integer === prec
end
if accept_zero
raise ArgumentError, "Negative precision for #{method_name}" if prec < 0
else
raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0
end
prec
end
def self.infinity_computation_result # :nodoc:
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY)
raise FloatDomainError, "Computation results in 'Infinity'"
end
BigDecimal::INFINITY
end
def self.underflow_computation_result # :nodoc:
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_UNDERFLOW)
raise FloatDomainError, 'Exponent underflow'
end
BigDecimal(0)
end
def self.nan_computation_result # :nodoc:
if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN)
raise FloatDomainError, "Computation results to 'NaN'"
end
BigDecimal::NAN
end
# Iteration for Newton's method with increasing precision
def self.newton_loop(prec, initial_precision: BigDecimal.double_fig / 2, safe_margin: 2) # :nodoc:
precs = []
while prec > initial_precision
precs << prec
prec = (precs.last + 1) / 2 + safe_margin
end
precs.reverse_each do |p|
yield p
end
end
# Calculates Math.log(x.to_f) considering large or small exponent
def self.float_log(x) # :nodoc:
Math.log(x._decimal_shift(-x.exponent).to_f) + x.exponent * Math.log(10)
end
# Calculating Taylor series sum using binary splitting method
# Calculates f(x) = (x/d0)*(1+(x/d1)*(1+(x/d2)*(1+(x/d3)*(1+...))))
# x.n_significant_digits or ds.size must be small to be performant.
def self.taylor_sum_binary_splitting(x, ds, prec) # :nodoc:
fs = ds.map {|d| [0, BigDecimal(d)] }
# fs = [[a0, a1], [b0, b1], [c0, c1], ...]
# f(x) = a0/a1+(x/a1)*(1+b0/b1+(x/b1)*(1+c0/c1+(x/c1)*(1+d0/d1+(x/d1)*(1+...))))
while fs.size > 1
# Merge two adjacent fractions
# from: (1 + a0/a1 + x/a1 * (1 + b0/b1 + x/b1 * rest))
# to: (1 + (a0*b1+x*(b0+b1))/(a1*b1) + (x*x)/(a1*b1) * rest)
xn = xn ? xn.mult(xn, prec) : x
fs = fs.each_slice(2).map do |(a, b)|
b ||= [0, BigDecimal(1)._decimal_shift([xn.exponent, 0].max + 2)]
[
(a[0] * b[1]).add(xn * (b[0] + b[1]), prec),
a[1].mult(b[1], prec)
]
end
end
BigDecimal(fs[0][0]).div(fs[0][1], prec)
end
end
# call-seq:
# self ** other -> bigdecimal
#
# Returns the \BigDecimal value of +self+ raised to power +other+:
#
# b = BigDecimal('3.14')
# b ** 2 # => 0.98596e1
# b ** 2.0 # => 0.98596e1
# b ** Rational(2, 1) # => 0.98596e1
#
# Related: BigDecimal#power.
#
def **(y)
case y
when BigDecimal, Integer, Float, Rational
power(y)
when nil
raise TypeError, 'wrong argument type NilClass'
else
x, y = y.coerce(self)
x**y
end
end
# call-seq:
# power(n)
# power(n, prec)
#
# Returns the value raised to the power of n.
#
# Also available as the operator **.
#
def power(y, prec = 0)
prec = Internal.coerce_validate_prec(prec, :power, accept_zero: true)
x = self
y = Internal.coerce_to_bigdecimal(y, prec.nonzero? || n_significant_digits, :power)
return Internal.nan_computation_result if x.nan? || y.nan?
return BigDecimal(1) if y.zero?
if y.infinite?
if x < 0
return BigDecimal(0) if x < -1 && y.negative?
return BigDecimal(0) if x > -1 && y.positive?
raise Math::DomainError, 'Result undefined for negative base raised to infinite power'
elsif x < 1
return y.positive? ? BigDecimal(0) : BigDecimal::Internal.infinity_computation_result
elsif x == 1
return BigDecimal(1)
else
return y.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)
end
end
if x.infinite? && y < 0
# Computation result will be +0 or -0. Avoid overflow.
neg = x < 0 && y.frac.zero? && y % 2 == 1
return neg ? -BigDecimal(0) : BigDecimal(0)
end
if x.zero?
return BigDecimal(1) if y.zero?
return BigDecimal(0) if y > 0
if y.frac.zero? && y % 2 == 1 && x.sign == -1
return -BigDecimal::Internal.infinity_computation_result
else
return BigDecimal::Internal.infinity_computation_result
end
elsif x < 0
if y.frac.zero?
if y % 2 == 0
return (-x).power(y, prec)
else
return -(-x).power(y, prec)
end
else
raise Math::DomainError, 'Computation results in complex number'
end
elsif x == 1
return BigDecimal(1)
end
limit = BigDecimal.limit
frac_part = y.frac
if frac_part.zero? && prec.zero? && limit.zero?
# Infinite precision calculation for `x ** int` and `x.power(int)`
int_part = y.fix.to_i
int_part = -int_part if (neg = int_part < 0)
ans = BigDecimal(1)
n = 1
xn = x
while true
ans *= xn if int_part.allbits?(n)
n <<= 1
break if n > int_part
xn *= xn
# Detect overflow/underflow before consuming infinite memory
if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF
return ((xn.exponent > 0) ^ neg ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1)
end
end
return neg ? BigDecimal(1) / ans : ans
end
result_prec = prec.nonzero? || [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig
result_prec = [result_prec, limit].min if prec.zero? && limit.nonzero?
prec2 = result_prec + BigDecimal::Internal::EXTRA_PREC
if y < 0
inv = x.power(-y, prec2)
return BigDecimal(0) if inv.infinite?
return BigDecimal::Internal.infinity_computation_result if inv.zero?
return BigDecimal(1).div(inv, result_prec)
end
if frac_part.zero? && y.exponent < Math.log(result_prec) * 5 + 20
# Use exponentiation by squaring if y is an integer and not too large
pow_prec = prec2 + y.exponent
n = 1
xn = x
ans = BigDecimal(1)
int_part = y.fix.to_i
while true
ans = ans.mult(xn, pow_prec) if int_part.allbits?(n)
n <<= 1
break if n > int_part
xn = xn.mult(xn, pow_prec)
end
ans.mult(1, result_prec)
else
if x > 1 && x.finite?
# To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0.
# Estimate (y*log(x)).exponent
logx_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round
ylogx_exponent = y.exponent + logx_exponent
prec2 += [ylogx_exponent, 0].max
end
BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), result_prec)
end
end
# Returns the square root of the value.
#
# Result has at least prec significant digits.
#
def sqrt(prec)
prec = Internal.coerce_validate_prec(prec, :sqrt, accept_zero: true)
return Internal.infinity_computation_result if infinite? == 1
raise FloatDomainError, 'sqrt of negative value' if self < 0
raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan?
return self if zero?
if prec == 0
limit = BigDecimal.limit
prec = n_significant_digits + BigDecimal.double_fig
prec = [limit, prec].min if limit.nonzero?
end
ex = exponent / 2
x = _decimal_shift(-2 * ex)
y = BigDecimal(Math.sqrt(x.to_f), 0)
Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p|
y = y.add(x.div(y, p), p).div(2, p)
end
y._decimal_shift(ex).mult(1, prec)
end
end
# Core BigMath methods for BigDecimal (log, exp) are defined here.
# Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'.
module BigMath
module_function
# call-seq:
# BigMath.log(decimal, numeric) -> BigDecimal
#
# Computes the natural logarithm of +decimal+ to the specified number of
# digits of precision, +numeric+.
#
# If +decimal+ is zero or negative, raises Math::DomainError.
#
# If +decimal+ is positive infinity, returns Infinity.
#
# If +decimal+ is NaN, returns NaN.
#
def log(x, prec)
prec = BigDecimal::Internal.coerce_validate_prec(prec, :log)
raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log)
return BigDecimal::Internal.nan_computation_result if x.nan?
raise Math::DomainError, 'Negative argument for log' if x < 0
return -BigDecimal::Internal.infinity_computation_result if x.zero?
return BigDecimal::Internal.infinity_computation_result if x.infinite?
return BigDecimal(0) if x == 1
prec2 = prec + BigDecimal::Internal::EXTRA_PREC
# Reduce x to near 1
if x > 1.01 || x < 0.99
# log(x) = log(x/exp(logx_approx)) + logx_approx
logx_approx = BigDecimal(BigDecimal::Internal.float_log(x), 0)
x = x.div(exp(logx_approx, prec2), prec2)
else
logx_approx = BigDecimal(0)
end
# Solve exp(y) - x = 0 with Newton's method
# Repeat: y -= (exp(y) - x) / exp(y)
y = BigDecimal(BigDecimal::Internal.float_log(x), 0)
exp_additional_prec = [-(x - 1).exponent, 0].max
BigDecimal::Internal.newton_loop(prec2) do |p|
expy = exp(y, p + exp_additional_prec)
y = y.sub(expy.sub(x, p).div(expy, p), p)
end
y.add(logx_approx, prec)
end
private_class_method def _exp_binary_splitting(x, prec) # :nodoc:
return BigDecimal(1) if x.zero?
# Find k that satisfies x**k / k! < 10**(-prec)
log10 = Math.log(10)
logx = BigDecimal::Internal.float_log(x.abs)
step = (1..).bsearch { |k| Math.lgamma(k + 1)[0] - k * logx > prec * log10 }
# exp(x)-1 = x*(1+x/2*(1+x/3*(1+x/4*(1+x/5*(1+...)))))
1 + BigDecimal::Internal.taylor_sum_binary_splitting(x, [*1..step], prec)
end
# call-seq:
# BigMath.exp(decimal, numeric) -> BigDecimal
#
# Computes the value of e (the base of natural logarithms) raised to the
# power of +decimal+, to the specified number of digits of precision.
#
# If +decimal+ is infinity, returns Infinity.
#
# If +decimal+ is NaN, returns NaN.
#
def exp(x, prec)
prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp)
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp)
return BigDecimal::Internal.nan_computation_result if x.nan?
if x.infinite? || x.exponent >= 21 # exp(10**20) and exp(-10**20) overflows/underflows 64-bit exponent
if x.positive?
return BigDecimal::Internal.infinity_computation_result
elsif x.infinite?
# exp(-Infinity) is +0 by definition, this is not an underflow.
return BigDecimal(0)
else
return BigDecimal::Internal.underflow_computation_result
end
end
return BigDecimal(1) if x.zero?
# exp(x * 10**cnt) = exp(x)**(10**cnt)
cnt = x < -1 || x > 1 ? x.exponent : 0
prec2 = prec + BigDecimal::Internal::EXTRA_PREC + cnt
x = x._decimal_shift(-cnt)
# Decimal form of bit-burst algorithm
# Calculate exp(x.xxxxxxxxxxxxxxxx) as
# exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx)
x = x.mult(1, prec2)
n = 2
y = BigDecimal(1)
BigDecimal.save_limit do
BigDecimal.limit(0)
while x != 0 do
partial_x = x.truncate(n)
x -= partial_x
y = y.mult(_exp_binary_splitting(partial_x, prec2), prec2)
n *= 2
end
end
# calculate exp(x * 10**cnt) from exp(x)
# exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10
cnt.times do
y2 = y.mult(y, prec2)
y5 = y2.mult(y2, prec2).mult(y, prec2)
y = y5.mult(y5, prec2)
end
y.mult(1, prec)
end
end
|