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.
@@ -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