bigdecimal 4.1.2 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bigdecimal.gemspec +2 -0
- data/ext/bigdecimal/bigdecimal.c +26 -10
- data/ext/bigdecimal/bigdecimal.h +5 -5
- data/ext/bigdecimal/div.h +61 -34
- data/lib/bigdecimal/math/erf.rb +291 -0
- data/lib/bigdecimal/math/gamma.rb +513 -0
- data/lib/bigdecimal/math.rb +8 -219
- data/sig/big_decimal.rbs +3 -1
- metadata +3 -1
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'bigdecimal/math'
|
|
3
|
+
|
|
4
|
+
module BigMath
|
|
5
|
+
|
|
6
|
+
# Calculates gamma/lgamma
|
|
7
|
+
# Algorithm overview:
|
|
8
|
+
#
|
|
9
|
+
# Lagrange interpolation of f(x) = b**x / x! at integer nodes x_i = b-l, ..., b+l.
|
|
10
|
+
# BSM(Binary Splitting Method) version for small digit numbers, O(PREC*log(PREC)^3)
|
|
11
|
+
# BSGS(Baby-Step Giant-Step) version for full digit numbers, O(PREC^2*log(log(PREC)))
|
|
12
|
+
# Both orders of magnitude faster than Spouge's approximation which is O(PREC^2*log(PREC))
|
|
13
|
+
# (Complexities assume quasi-linear multiplication, counting large-by-small products
|
|
14
|
+
# as (n/m) * M(m) = n * log(m) bit ops. BigDecimal multiplies the small coefficients
|
|
15
|
+
# by schoolbook instead: an extra log factor asymptotically, but faster at any feasible PREC.)
|
|
16
|
+
# Requires fast factorial of an integer near x (see Factorial Doubling below).
|
|
17
|
+
#
|
|
18
|
+
# Factorial Doubling for fast calculation of large factorials:
|
|
19
|
+
# Using Legendre duplication formula, we can calculate factorial(2n) from factorial(n) and factorial(n + 0.5).
|
|
20
|
+
# Calculating factorial(n + 0.5) is done by the BSM version of Lagrange interpolation in quasi-linear time.
|
|
21
|
+
# This will drastically reduce the cost of calculating large factorials.
|
|
22
|
+
# O(PREC*log(PREC)^3*log(factorial_argument))
|
|
23
|
+
#
|
|
24
|
+
# Stirling's approximation with Bernoulli numbers
|
|
25
|
+
# Only used when x is extremely large.
|
|
26
|
+
|
|
27
|
+
module Gamma # :nodoc:
|
|
28
|
+
|
|
29
|
+
# Calculates gamma function with given precision.
|
|
30
|
+
def self.gamma(x, prec)
|
|
31
|
+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :gamma)
|
|
32
|
+
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma)
|
|
33
|
+
prec2 = prec + BigDecimal::Internal::EXTRA_PREC
|
|
34
|
+
|
|
35
|
+
if x < 0.5
|
|
36
|
+
raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero?
|
|
37
|
+
|
|
38
|
+
# Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z)
|
|
39
|
+
pi = BigMath::PI(prec2)
|
|
40
|
+
sin = sinpix(x, pi, prec2)
|
|
41
|
+
pi.div(gamma(1 - x, prec2).mult(sin, prec2), prec)
|
|
42
|
+
else
|
|
43
|
+
# Digits of x beyond the working precision cannot affect the result.
|
|
44
|
+
# Rounding must happen before the integer test: an x indistinguishable from
|
|
45
|
+
# an integer must take the exact integer path, because gamma_lagrange
|
|
46
|
+
# requires a non-integer x (an integer x makes a node distance exactly zero).
|
|
47
|
+
x = x.mult(1, prec2 + x.exponent + 10)
|
|
48
|
+
if x.frac.zero?
|
|
49
|
+
integer_factorial(x.to_i - 1, prec2).mult(1, prec)
|
|
50
|
+
else
|
|
51
|
+
base, large_factorial_arg, small_factorial_arg, exp2 = gamma_lagrange(x, prec2)
|
|
52
|
+
ans = base.mult(integer_factorial(small_factorial_arg, prec2), prec2)
|
|
53
|
+
ans = ans.mult(BigDecimal(2).power(exp2, prec2), prec2) unless exp2.zero?
|
|
54
|
+
ans.mult(integer_factorial(large_factorial_arg, prec2), prec)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Calculates log gamma and its sign with given precision.
|
|
60
|
+
def self.lgamma(x, prec)
|
|
61
|
+
prec = BigDecimal::Internal.coerce_validate_prec(prec, :lgamma)
|
|
62
|
+
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :lgamma)
|
|
63
|
+
prec2 = prec + BigDecimal::Internal::EXTRA_PREC
|
|
64
|
+
if x < 0.5
|
|
65
|
+
return [BigDecimal::INFINITY, 1] if x.frac.zero?
|
|
66
|
+
|
|
67
|
+
loop do
|
|
68
|
+
# Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z)
|
|
69
|
+
pi = BigMath::PI(prec2)
|
|
70
|
+
sin = sinpix(x, pi, prec2)
|
|
71
|
+
log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec)
|
|
72
|
+
return [log_gamma, sin > 0 ? 1 : -1] if log_gamma != 0 && prec2 + log_gamma.exponent > prec + BigDecimal::Internal::EXTRA_PREC
|
|
73
|
+
|
|
74
|
+
# Retry with higher precision if loss of significance is too large
|
|
75
|
+
prec2 = prec2 * 3 / 2
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
# if x is close to 1 or 2, increase precision to reduce loss of significance
|
|
79
|
+
diff1_exponent = x < 3 ? (x - 1).exponent : 0
|
|
80
|
+
diff2_exponent = x < 3 ? (x - 2).exponent : 0
|
|
81
|
+
extremely_near_one = diff1_exponent < -prec2
|
|
82
|
+
extremely_near_two = diff2_exponent < -prec2
|
|
83
|
+
|
|
84
|
+
if extremely_near_one || extremely_near_two
|
|
85
|
+
# If x is extremely close to base = 1 or 2, linear interpolation is accurate enough.
|
|
86
|
+
# Taylor expansion at x = base is: (x - base) * digamma(base) + (x - base) ** 2 * trigamma(base) / 2 + ...
|
|
87
|
+
# And we can ignore (x - base) ** 2 and higher order terms.
|
|
88
|
+
base = extremely_near_one ? 1 : 2
|
|
89
|
+
d = BigDecimal(1)._decimal_shift(1 - prec2)
|
|
90
|
+
log_gamma_d, sign = lgamma(base + d, prec2)
|
|
91
|
+
return [log_gamma_d.mult(x - base, prec2).div(d, prec), sign]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
prec2 += [-diff1_exponent, -diff2_exponent, 0].max
|
|
95
|
+
|
|
96
|
+
# Same rounding as in Gamma.gamma. Must come after the near 1 and 2 handling
|
|
97
|
+
# above, which needs the exact distance from x to 1 or 2.
|
|
98
|
+
x = x.mult(1, prec2 + x.exponent + 10)
|
|
99
|
+
|
|
100
|
+
# When x is extremely large, the cost of Bernoulli number generation for Stirling's
|
|
101
|
+
# asymptotic expansion is smaller than the cost of multiple steps of doubling method.
|
|
102
|
+
# The condition is based on heuristic cost estimation and empirical tuning.
|
|
103
|
+
if x > prec2 && x.exponent > Integer.sqrt(prec2) / 6
|
|
104
|
+
[lgamma_stirling(x, prec2).mult(1, prec), 1]
|
|
105
|
+
elsif x.frac.zero?
|
|
106
|
+
[integer_factorial_log(x.to_i - 1, prec2).mult(1, prec), 1]
|
|
107
|
+
else
|
|
108
|
+
base, large_factorial_arg, small_factorial_arg, exp2 = gamma_lagrange(x, prec2)
|
|
109
|
+
lgamma = BigMath.log(base, prec2)
|
|
110
|
+
lgamma = lgamma.add(BigMath.log(2, prec2) * exp2, prec2) unless exp2.zero?
|
|
111
|
+
lgamma = lgamma.add(integer_factorial_log(small_factorial_arg, prec2), prec2)
|
|
112
|
+
lgamma = lgamma.add(integer_factorial_log(large_factorial_arg, prec2), prec)
|
|
113
|
+
[lgamma, 1]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Calculates prod { x - k } for k in ks and the coefficients of the expanded polynomial.
|
|
119
|
+
# xn is an array of precalculated powers of x: [1, x, x**2, x**3, ...]
|
|
120
|
+
def self.x_minus_k_prod_coef(ks, xn, prec)
|
|
121
|
+
coef = [1]
|
|
122
|
+
ks.each do |k|
|
|
123
|
+
coef_next = [0] * (coef.size + 1)
|
|
124
|
+
coef.each_with_index do |c, i|
|
|
125
|
+
coef_next[i] -= k * c
|
|
126
|
+
coef_next[i + 1] += c
|
|
127
|
+
end
|
|
128
|
+
coef = coef_next
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
prod = coef.each_with_index.map do |c, i|
|
|
132
|
+
xn[i].mult(c, prec)
|
|
133
|
+
end.reduce do |sum, value|
|
|
134
|
+
sum.add(value, prec)
|
|
135
|
+
end
|
|
136
|
+
[prod, coef]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Bit length to keep in bit-dropping integer products and fraction merges.
|
|
140
|
+
# Only about prec * log2(10) bits are needed. 10 / 3 slightly exceeds log2(10),
|
|
141
|
+
# and 64 extra bits absorb the ~1 bit lost per truncation over the tree depth.
|
|
142
|
+
def self.drop_cap_bits(prec)
|
|
143
|
+
(prec * 10 + 192) / 3
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Calculate numbers.reduce(:*) of integers by Binary Splitting Method.
|
|
147
|
+
# Returns [mantissa, exp2] representing mantissa * 2**exp2.
|
|
148
|
+
# With cap, lower bits of intermediate products are dropped to keep each
|
|
149
|
+
# multiplication cost bounded by the target precision. Without cap, the product is exact.
|
|
150
|
+
def self.int_bsm_prod(numbers, cap = nil)
|
|
151
|
+
numbers = numbers.to_a
|
|
152
|
+
exp2 = 0
|
|
153
|
+
while numbers.size > 1
|
|
154
|
+
numbers = numbers.each_slice(2).map do |a, b|
|
|
155
|
+
next a unless b
|
|
156
|
+
v = a * b
|
|
157
|
+
if cap && (s = v.bit_length - cap) > 0
|
|
158
|
+
exp2 += s
|
|
159
|
+
v >>= s
|
|
160
|
+
end
|
|
161
|
+
v
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
[numbers.first || 1, exp2]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Calculate factorial for integer n
|
|
168
|
+
def self.integer_factorial(n, prec)
|
|
169
|
+
power_part, exp2, exp_sqrtpi = integer_factorial_parameter(n, prec)
|
|
170
|
+
ans = BigDecimal(2).power(exp2, prec)
|
|
171
|
+
power_part.each_with_index do |base, index|
|
|
172
|
+
ans = ans.mult(base.power(1 << index, prec), prec)
|
|
173
|
+
end
|
|
174
|
+
if exp_sqrtpi != 0
|
|
175
|
+
pi = BigMath::PI(doubling_level_prec(prec, power_part.size - 1))
|
|
176
|
+
# exp_sqrtpi is 2**k - 1 (odd): the doubling recursion squares the child's
|
|
177
|
+
# sqrt(pi) exponent and adds one, so only the last level's sqrt survives
|
|
178
|
+
pipow = pi.power(exp_sqrtpi / 2, prec).mult(pi.sqrt(prec), prec)
|
|
179
|
+
ans = ans.div(pipow, prec)
|
|
180
|
+
end
|
|
181
|
+
ans
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Precision for values that integer_factorial raises to the power 2**index.
|
|
185
|
+
# The power multiplies their relative error by 2**index, so they need
|
|
186
|
+
# index * log10(2) more digits than the result.
|
|
187
|
+
def self.doubling_level_prec(prec, index)
|
|
188
|
+
prec + (index * Math.log10(2)).ceil + 1
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Calculate log factorial for integer n
|
|
192
|
+
def self.integer_factorial_log(n, prec)
|
|
193
|
+
power_part, exp2, exp_sqrtpi = integer_factorial_parameter(n, prec)
|
|
194
|
+
ans = exp2.zero? ? BigDecimal(0) : BigMath.log(2, prec) * exp2
|
|
195
|
+
power_part.each_with_index do |base, index|
|
|
196
|
+
ans = ans.add(BigMath.log(base, prec) * (1 << index), prec)
|
|
197
|
+
end
|
|
198
|
+
if exp_sqrtpi != 0
|
|
199
|
+
pi = BigMath::PI(prec)
|
|
200
|
+
ans = ans.sub(BigMath.log(pi, prec) * (BigDecimal(exp_sqrtpi) / 2), prec)
|
|
201
|
+
end
|
|
202
|
+
ans
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Calculates parameters for integer factorial calculation.
|
|
206
|
+
# Returns [base_power_part, exp2, exp_sqrtpi] that can produce factorial(n) as:
|
|
207
|
+
# factorial(n) = prod { base_power_part[i]**(1 << i) } * 2**exp2 / sqrt(pi)**exp_sqrtpi
|
|
208
|
+
# These parameters are used to avoid overflow when calculating log factorial and lgamma for large n.
|
|
209
|
+
def self.integer_factorial_parameter(n, prec)
|
|
210
|
+
base_power_part, factorial_power_part, exp2, exp_sqrtpi = integer_factorial_recursive(n, prec)
|
|
211
|
+
fact_x = 1
|
|
212
|
+
fact_y = BigDecimal(1)
|
|
213
|
+
# factorial_power_part is non-decreasing (deeper recursion levels have smaller b,
|
|
214
|
+
# and gamma_lagrange_l grows as b shrinks), so fact_y can be extended incrementally.
|
|
215
|
+
# fact_y is carried over to every later index, so it needs the precision
|
|
216
|
+
# of the deepest level from the start.
|
|
217
|
+
level_prec = doubling_level_prec(prec, factorial_power_part.size - 1)
|
|
218
|
+
factorial_power_part.each_with_index do |factorial_arg, index|
|
|
219
|
+
# Exact product (no bit drop): these ranges total only O(prec * log(prec)) digits,
|
|
220
|
+
# and a dropped 2**s here would be raised to 2**index, exceeding the representable
|
|
221
|
+
# exponent range while base_power_part[index] underflows by the same amount.
|
|
222
|
+
mantissa, = int_bsm_prod(fact_x + 1..factorial_arg)
|
|
223
|
+
fact_y = fact_y.mult(mantissa, level_prec)
|
|
224
|
+
fact_x = factorial_arg
|
|
225
|
+
base_power_part[index] = base_power_part[index].mult(fact_y, level_prec)
|
|
226
|
+
end
|
|
227
|
+
[base_power_part, exp2, exp_sqrtpi]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Returns [base_power_part, factorial_power_part, exp2, exp_sqrtpi] that can produce factorial(n) as:
|
|
231
|
+
# factorial(n) = prod { base_power_part[i]**(1 << i) } * prod { factorial(factorial_power_part[i])**(1 << i) } * 2**exp2 / sqrt(pi)**(exp_sqrtpi)
|
|
232
|
+
# If n is large, this method recursively calculates factorial for smaller n by Legendre duplication formula.
|
|
233
|
+
# index is the recursion depth, which is also the position in the returned arrays.
|
|
234
|
+
def self.integer_factorial_recursive(n, prec, index = 0)
|
|
235
|
+
level_prec = doubling_level_prec(prec, index)
|
|
236
|
+
if n < 4 * prec
|
|
237
|
+
mantissa, exp2 = int_bsm_prod(1..n, drop_cap_bits(level_prec))
|
|
238
|
+
return [[BigDecimal(mantissa)], [], exp2, 0]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Use Legendre duplication formula to reduce factorial(n) to half-size factorials:
|
|
242
|
+
# factorial(n) = factorial(n/2.0) * factorial((n-1)/2.0) * 2**n / sqrt(pi)
|
|
243
|
+
# gamma_lagrange((n + 1) / 2 + 0.5, prec) computes the half-integer factorial
|
|
244
|
+
# (whichever of the two factors above is a half-integer).
|
|
245
|
+
half_arg = BigDecimal((n + 1) / 2) + BigDecimal('0.5')
|
|
246
|
+
base, large_factorial_arg, small_factorial_arg, lagrange_exp2 = gamma_lagrange(half_arg, level_prec)
|
|
247
|
+
|
|
248
|
+
range_mantissa, = int_bsm_prod(large_factorial_arg + 1..n / 2) # exact: total size is O(prec) digits
|
|
249
|
+
base = base.mult(range_mantissa, level_prec)
|
|
250
|
+
base_power_part, factorial_power_part, exp2, exp_sqrtpi = integer_factorial_recursive(large_factorial_arg, prec, index + 1)
|
|
251
|
+
[
|
|
252
|
+
[base] + base_power_part,
|
|
253
|
+
[small_factorial_arg] + factorial_power_part,
|
|
254
|
+
exp2 * 2 + n + lagrange_exp2,
|
|
255
|
+
exp_sqrtpi * 2 + 1
|
|
256
|
+
]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Estimate the required number of interpolation points `l` to achieve `prec` digits.
|
|
260
|
+
#
|
|
261
|
+
# Assuming the nodes stay strictly positive, the function b^x/x! approximates a
|
|
262
|
+
# Gaussian curve e^(-y^2 / 2b) around its peak (x=b).
|
|
263
|
+
# The Taylor coefficient of degree 2l is roughly c_2l = 1 / (l! * (2b)^l).
|
|
264
|
+
# Multiplying this by the distance product of 2l+1 nodes (approx (l/e)^(2l)),
|
|
265
|
+
# the overall truncation error E is bounded by: E ~ (l / 2eb)^l.
|
|
266
|
+
#
|
|
267
|
+
# Setting E <= 10^-prec gives the implicit equation:
|
|
268
|
+
# l * log10(2 * e * b / l) = prec => l = prec / log10(2 * e * b / l)
|
|
269
|
+
def self.gamma_lagrange_l(b, prec)
|
|
270
|
+
# Initial guess of l. When b >= 2 * prec - 1 (guaranteed by the shift in gamma_lagrange),
|
|
271
|
+
# this is safely larger than the actual l.
|
|
272
|
+
l = prec
|
|
273
|
+
|
|
274
|
+
# Solves the implicit equation via fixed-point iteration.
|
|
275
|
+
# Due to the slow growth of the logarithm, 2 iterations are practically sufficient.
|
|
276
|
+
2.times { l = prec / Math.log10(2 * Math::E * b / l) }
|
|
277
|
+
l.ceil + 10 # Adds safety margin
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Calculate approximate gamma by Lagrange interpolation of f(x) = b**x / x!
|
|
281
|
+
# Nodes are placed at x_i = b-l, b-l+1, ..., b+l.
|
|
282
|
+
# b: x.round, l: number of nodes on one side (total nodes = 2*l+1)
|
|
283
|
+
#
|
|
284
|
+
# Mathematically, we use the barycentric interpolation form:
|
|
285
|
+
# f(x) \approx \omega(x) \sum_{i} \frac{w_i f(x_i)}{x - x_i}
|
|
286
|
+
# Therefore, \Gamma(x+1) = x! = b**x / f(x)
|
|
287
|
+
#
|
|
288
|
+
# Time complexity:
|
|
289
|
+
# - O(PREC*log(PREC)^3) for small-digit x (Binary Splitting)
|
|
290
|
+
# - O(PREC^2*log(log(PREC))) for full-digit x (Baby-step Giant-step).
|
|
291
|
+
# Measured time grows like PREC^2.
|
|
292
|
+
#
|
|
293
|
+
# Returns [base, large_factorial_arg, small_factorial_arg, exp2] that can produce gamma(x) as:
|
|
294
|
+
# gamma(x) = base * 2**exp2 * factorial(large_factorial_arg) * factorial(small_factorial_arg)
|
|
295
|
+
def self.gamma_lagrange(x, prec)
|
|
296
|
+
# Shift x to establish a safe center (b) for the barycentric interpolation.
|
|
297
|
+
#
|
|
298
|
+
# We must keep all interpolation nodes strictly positive (b - l > 0). Approaching
|
|
299
|
+
# x = 0 breaks the Gaussian approximation used to estimate `l` and provides no
|
|
300
|
+
# useful information for the interpolation.
|
|
301
|
+
#
|
|
302
|
+
# While b =~ 1.36 * prec is the strict theoretical minimum to stay positive, we
|
|
303
|
+
# heuristically use b = 2 * prec. This moves the nodes safely away from x = 0,
|
|
304
|
+
# stabilizes the curve, and empirically yields the optimal total computation cost.
|
|
305
|
+
# (See `gamma_lagrange_l` for the mathematical derivation of the approximation).
|
|
306
|
+
shift = x < 2 * prec ? 2 * prec - x.floor : 0
|
|
307
|
+
x += shift
|
|
308
|
+
|
|
309
|
+
x = BigDecimal(x) - 1
|
|
310
|
+
b = x.round
|
|
311
|
+
l = gamma_lagrange_l(b, prec)
|
|
312
|
+
exp2 = 0
|
|
313
|
+
|
|
314
|
+
# --- Reference: Naive interpolation logic ---
|
|
315
|
+
# The two branches below optimize this calculation for the full-digit and small-digit x cases.
|
|
316
|
+
# sum = BigDecimal(0)
|
|
317
|
+
# prod = [*(b - l..b + l), *(0...shift)].map {|i| x - i }.reduce { _1.mult(_2, prec) }
|
|
318
|
+
# c = BigDecimal(1) # represents w_i * f(x_i) (normalized)
|
|
319
|
+
# (b - l..b + l).each do |i|
|
|
320
|
+
# if i != b - l
|
|
321
|
+
# c = c.mult(-b * (b + l - i + 1), prec).div((i - b + l) * i, prec)
|
|
322
|
+
# end
|
|
323
|
+
# sum = sum.add(c.div(x - i, prec), prec)
|
|
324
|
+
# end
|
|
325
|
+
# --------------------------------------------
|
|
326
|
+
|
|
327
|
+
# Choose between BSM and BSGS based on total bit cost:
|
|
328
|
+
# BSM: (l * n_sig / prec) full-digit multiplications, each costing prec * log(prec)
|
|
329
|
+
# bit ops, total l * n_sig * log(prec).
|
|
330
|
+
# BSGS: l * prec bit ops with batch_size = log2(prec) (see below).
|
|
331
|
+
# Cross-over: n_sig * log(prec) > prec.
|
|
332
|
+
if x.n_significant_digits * prec.bit_length > prec
|
|
333
|
+
# Reduce full-precision multiplications/divisions using a Batched Evaluation
|
|
334
|
+
# inspired by the Baby-Step Giant-Step (BSGS) method.
|
|
335
|
+
|
|
336
|
+
# Normal BSGS uses batch_size = sqrt(l), but here the integer coefficients of the
|
|
337
|
+
# expanded prod { x - k } over a batch grow like (b + l)**batch_size, so a smaller
|
|
338
|
+
# batch keeps both the coefficient size and the per-batch evaluation cost low.
|
|
339
|
+
# batch_size = log2(prec) brings the total BSGS bit cost down to O(l * prec * log(log(prec))).
|
|
340
|
+
batch_size = prec.bit_length
|
|
341
|
+
|
|
342
|
+
# When expanding prod { x - k }, the coefficient of x**n might be huge.
|
|
343
|
+
# Increase internal calculation precision to avoid catastrophic cancellation.
|
|
344
|
+
# When x is within 10**-q of a node, batch_prod cancels by q more digits;
|
|
345
|
+
# without the extra digits the computed batch_prod can even collapse to
|
|
346
|
+
# exactly zero, and the division below would raise or produce NaN.
|
|
347
|
+
nearest_node_distance = x - x.round(0, BigDecimal::ROUND_HALF_UP)
|
|
348
|
+
near_node_digits = [1 - nearest_node_distance.exponent, 0].max
|
|
349
|
+
internal_xn_prec = prec + (Math.log10(b + l) * batch_size).ceil + near_node_digits
|
|
350
|
+
xn = [BigDecimal(1)]
|
|
351
|
+
xn << xn.last.mult(x, internal_xn_prec) while xn.size <= batch_size
|
|
352
|
+
|
|
353
|
+
c = BigDecimal(1)
|
|
354
|
+
sum = BigDecimal(0)
|
|
355
|
+
prod = BigDecimal(1)
|
|
356
|
+
|
|
357
|
+
# sum, c and prod are updated once per batch, so their rounding errors
|
|
358
|
+
# accumulate in proportion to the number of batches.
|
|
359
|
+
batch_count = (2 * l + 1 + shift) / batch_size + 1
|
|
360
|
+
accumulate_prec = prec + Math.log10(batch_count).ceil + 1
|
|
361
|
+
|
|
362
|
+
((b - l)..(b + l)).to_a.each_slice(batch_size) do |batch_ks|
|
|
363
|
+
# Calculate prod{ x - k } in this batch
|
|
364
|
+
batch_prod, prod_coef = x_minus_k_prod_coef(batch_ks, xn, internal_xn_prec)
|
|
365
|
+
|
|
366
|
+
# Calculate coefficients of batch_prod / (x - k) using Synthetic Division (Ruffini's rule)
|
|
367
|
+
batch_coef = [0] * batch_ks.size
|
|
368
|
+
c_scale = 1r
|
|
369
|
+
batch_ks.each do |k|
|
|
370
|
+
c_scale = c_scale * (-b * (b + l - k + 1)) / ((k - b + l) * k) if k != b - l
|
|
371
|
+
rem = 0
|
|
372
|
+
(batch_ks.size - 1).downto(0) do |i|
|
|
373
|
+
quo = prod_coef[i + 1] + rem
|
|
374
|
+
rem = quo * k
|
|
375
|
+
batch_coef[i] += c_scale * quo
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
batch_sum = BigDecimal(0)
|
|
380
|
+
batch_coef.each_with_index do |coef, i|
|
|
381
|
+
batch_sum = batch_sum.add(xn[i].mult(coef.numerator, internal_xn_prec).div(coef.denominator, internal_xn_prec), internal_xn_prec)
|
|
382
|
+
end
|
|
383
|
+
# batch_prod loses relative accuracy when x is extremely close to a node in this
|
|
384
|
+
# batch. This is harmless: the same computed value is divided into sum here and
|
|
385
|
+
# multiplied into prod below, so the error cancels in the final prod * sum.
|
|
386
|
+
sum = sum.add(batch_sum.mult(c, accumulate_prec).div(batch_prod, accumulate_prec), accumulate_prec)
|
|
387
|
+
c = c.mult(c_scale.numerator, accumulate_prec).div(c_scale.denominator, accumulate_prec)
|
|
388
|
+
prod = prod.mult(batch_prod, accumulate_prec)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Perform shift.times {|i| prod = prod.mult(x - i, prec) } with batch processing
|
|
392
|
+
shift.times.to_a.each_slice(batch_size) do |batch_ks|
|
|
393
|
+
shift_prod, _prod_coef = x_minus_k_prod_coef(batch_ks, xn, internal_xn_prec)
|
|
394
|
+
prod = prod.mult(shift_prod, accumulate_prec)
|
|
395
|
+
end
|
|
396
|
+
else
|
|
397
|
+
# Binary Splitting Method (BSM) for short-digit inputs.
|
|
398
|
+
# Scaling x by 10**frac_digits makes every node term x - i an exact integer,
|
|
399
|
+
# so the whole tree runs on Integer arithmetic. The scale cancels inside the
|
|
400
|
+
# fraction merges; only prod and sum need explicit rescaling.
|
|
401
|
+
frac_digits = [x.n_significant_digits - x.exponent, 0].max
|
|
402
|
+
s10 = 10**frac_digits
|
|
403
|
+
xs = x._decimal_shift(frac_digits).to_i
|
|
404
|
+
cap = drop_cap_bits(prec)
|
|
405
|
+
|
|
406
|
+
prod_factors = (b - l..b + l).map {|i| xs - i * s10 } + shift.times.map {|i| xs - i * s10 }
|
|
407
|
+
mantissa, dropped_exp2 = int_bsm_prod(prod_factors, cap)
|
|
408
|
+
prod = BigDecimal(mantissa)._decimal_shift(-frac_digits * prod_factors.size)
|
|
409
|
+
# prod is missing the dropped 2**dropped_exp2 factor and gamma is proportional
|
|
410
|
+
# to 1 / prod. Returning the compensation as exp2 lets the factorial doubling
|
|
411
|
+
# fold it into its own 2**exp2 channel instead of paying a power here.
|
|
412
|
+
exp2 = -dropped_exp2
|
|
413
|
+
|
|
414
|
+
# State represents a partial evaluation of the series as: [sum_num, mult_num, den]
|
|
415
|
+
# Conceptually, each state translates to the following mathematical expression:
|
|
416
|
+
# (sum_num / den) + (mult_num / den) * (rest_of_the_series)
|
|
417
|
+
#
|
|
418
|
+
# The initial state [denominator, numerator, denominator] simply represents:
|
|
419
|
+
# (denominator / denominator) + (numerator / denominator) * rest
|
|
420
|
+
# = 1 + (numerator / denominator) * rest
|
|
421
|
+
#
|
|
422
|
+
fractions = (b - l + 1..b + l).map do |i|
|
|
423
|
+
denominator = (xs - i * s10) * ((i - b + l) * i)
|
|
424
|
+
numerator = (xs - (i - 1) * s10) * (-b * (b + l - i + 1))
|
|
425
|
+
[denominator, numerator, denominator]
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
while fractions.size > 1
|
|
429
|
+
fractions = fractions.each_slice(2).map do |a, c|
|
|
430
|
+
c ||= [1, 0, 1]
|
|
431
|
+
# Merge operation for BSM:
|
|
432
|
+
# a[0]/a[2] + a[1]/a[2] * (c[0]/c[2] + c[1]/c[2] * rest)
|
|
433
|
+
# = (a[0]*c[2] + a[1]*c[0]) / (a[2]*c[2]) + (a[1]*c[1]) / (a[2]*c[2]) * rest
|
|
434
|
+
v0 = a[0] * c[2] + a[1] * c[0]
|
|
435
|
+
v1 = a[1] * c[1]
|
|
436
|
+
v2 = a[2] * c[2]
|
|
437
|
+
# Drop lower bits to avoid the integers growing too large; see drop_cap_bits.
|
|
438
|
+
# All three components share the shift, so the represented ratios are unchanged.
|
|
439
|
+
s = v2.bit_length - cap
|
|
440
|
+
if s > 0
|
|
441
|
+
v0 >>= s
|
|
442
|
+
v1 >>= s
|
|
443
|
+
v2 >>= s
|
|
444
|
+
end
|
|
445
|
+
[v0, v1, v2]
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
fraction = fractions.first
|
|
449
|
+
sum = BigDecimal((fraction[0] + fraction[1]) * s10).div(fraction[2] * (xs - (b - l) * s10), prec)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Reconstruct Gamma(x_original) by reversing the scaling and applying shift formula
|
|
453
|
+
e = x - (b - l)
|
|
454
|
+
if e.frac == 0.5
|
|
455
|
+
# The factorial doubling path always has x = integer + 0.5. For this exponent shape,
|
|
456
|
+
# integer power and sqrt are much cheaper than the exp/log based fractional power.
|
|
457
|
+
power_part = BigDecimal(b).power(e.to_i, prec).mult(BigDecimal(b).sqrt(prec), prec)
|
|
458
|
+
else
|
|
459
|
+
power_part = BigDecimal(b).power(e, prec)
|
|
460
|
+
end
|
|
461
|
+
base = power_part.div(prod.mult(sum, prec), prec)
|
|
462
|
+
large_factorial_arg = b - l
|
|
463
|
+
small_factorial_arg = 2 * l
|
|
464
|
+
[base, large_factorial_arg, small_factorial_arg, exp2]
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Calculates bernoulli number.
|
|
468
|
+
# bns: calculated bernoulli numbers for memoization
|
|
469
|
+
def self.bernoulli(n, bns, prec)
|
|
470
|
+
return bns[0] ||= BigDecimal(1) if n == 0
|
|
471
|
+
return bns[1] ||= BigDecimal(-0.5) if n == 1
|
|
472
|
+
return bns[n] ||= BigDecimal(0) if n.odd?
|
|
473
|
+
bns[n] ||= (
|
|
474
|
+
comb = 1
|
|
475
|
+
sum = BigDecimal(0)
|
|
476
|
+
n.times do |i|
|
|
477
|
+
sum = sum.add(comb * bernoulli(i, bns, prec), prec)
|
|
478
|
+
comb = comb * (n - i + 1) / (i + 1)
|
|
479
|
+
end
|
|
480
|
+
sum.div(-n - 1, prec)
|
|
481
|
+
)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Calculate log gamma using Stirling's asymptotic expansion.
|
|
485
|
+
# While the condition of this asymptotic expansion is x > prec * log(10) / 2 / pi,
|
|
486
|
+
# we'll use this method only when x is extremely large to reduce the cost of Bernoulli number generation.
|
|
487
|
+
def self.lgamma_stirling(x, prec)
|
|
488
|
+
x = BigDecimal(x)
|
|
489
|
+
y = (x * (BigMath.log(x, prec) - 1)).add(BigMath.log(2 * BigMath::PI(prec).div(x, prec), prec) / 2, prec)
|
|
490
|
+
bns = []
|
|
491
|
+
xn = x
|
|
492
|
+
x2 = x.mult(x, prec)
|
|
493
|
+
(1..).each do |k|
|
|
494
|
+
xn = xn.mult(x2, prec) if k != 1
|
|
495
|
+
d = bernoulli(2 * k, bns, prec).div(xn, prec).div(2 * k * (2 * k - 1), prec)
|
|
496
|
+
y = y.add(d, prec)
|
|
497
|
+
break if d.exponent < y.exponent - prec
|
|
498
|
+
end
|
|
499
|
+
y
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Returns sin(pi * x), for gamma reflection formula calculation
|
|
503
|
+
def self.sinpix(x, pi, prec)
|
|
504
|
+
x = x % 2
|
|
505
|
+
sign = x > 1 ? -1 : 1
|
|
506
|
+
x %= 1
|
|
507
|
+
x = 1 - x if x > 0.5 # to avoid sin(pi*x) loss of precision for x close to 1
|
|
508
|
+
sign * BigMath.sin(x.mult(pi, prec), prec)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
private_constant :Gamma
|
|
513
|
+
end
|