Skip to content

Commit fb294ee

Browse files
committed
optimize full-digit
1 parent 7bf84d5 commit fb294ee

1 file changed

Lines changed: 77 additions & 9 deletions

File tree

lib/bigdecimal/math.rb

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -743,32 +743,100 @@ def gamma(x, prec)
743743
(x + (a - 1)).power(x - 0.5, prec2).mult(BigMath.exp(1 - x, prec2), prec2).mult(sum, prec)
744744
end
745745

746+
# Calculates prod{x-k} and its coefficients for given ks, xn and prec with baby-step giant-step method.
747+
# xn is an array of precalculated powers of x: [1, x, x**2, x**3, ...]
748+
private_class_method def _x_minus_k_prod_coef(ks, xn, prec)
749+
coef = [1]
750+
ks.each do |k|
751+
coef_next = [0] * (coef.size + 1)
752+
coef.each_with_index do |c, i|
753+
coef_next[i] -= k * c
754+
coef_next[i + 1] += c
755+
end
756+
coef = coef_next
757+
end
758+
759+
prd = coef.each_with_index.map do |c, i|
760+
xn[i].mult(c, prec)
761+
end.reduce do |sum, value|
762+
sum.add(value, prec)
763+
end
764+
[prd, coef]
765+
end
766+
746767
def gamma_lagrange(x, prec)
768+
# Calculate approximage gamma by Lagrange interpolation of b**x/x! at x=b-l, b-l+1, ..., b+l
769+
747770
x = BigDecimal(x) - 1
748771
l = prec
749772
shift = x < 2 * prec ? 2 * prec - x.floor : 0
750773
x += shift
751-
# Lagrange interpolate of b**x/x! at x=b-l, b-l+1, ..., b+l
752774
b = x.round
753-
prods = ((b-l)..(b+l)).map {|i| x - i } + shift.times.map {|i| x - i }
754-
prods = prods.each_slice(2).map {|a, b| b ? a.mult(b, prec) : a } while prods.size != 1
755-
prod = prods.first
756775

757776
c0s = [*(1..b-l), *(1..2*l)]
758777
c0s = c0s.each_slice(2).map {|a, b| b ? BigDecimal(a).mult(b, prec) : BigDecimal(a) } while c0s.size != 1
759778
c0 = c0s.first
760779

780+
# Optimize this calculation for full-digit-x case and small-digit-x case
781+
# sum = BigDecimal(0)
782+
# prod = [*(b-l)..(b+l), *0...shift).map {|i| x - i }.reduce { _1.mult(_2, prec) }
783+
# c = BigDecimal(1)
784+
# ((b-l)..(b+l)).each do |i|
785+
# if i != b - l
786+
# c = c.mult(-b * (b + l - i + 1), prec).div((i - b + l) * i, prec)
787+
# end
788+
# sum = sum.add(c.div(x - i, prec), prec)
789+
# end
761790
if x.n_significant_digits > prec / 10
762-
sum = BigDecimal(0)
791+
# Reduce multiplications by baby-step giant-step method
792+
793+
batch_size = prec.bit_length
794+
# When expanding prod{x-k}, the coefficient of x**n might be huge.
795+
# Increase internal calculation precision to avoid loss of precision due to cancellation.
796+
internal_xn_prec = prec + (Math.log10(b + l) * batch_size).ceil
797+
xn = [BigDecimal(1)]
798+
xn << xn.last.mult(x, internal_xn_prec) while xn.size <= batch_size
799+
763800
c = BigDecimal(1)
764-
((b-l)..(b+l)).each do |i|
765-
if i != b - l
766-
c = c.mult(-b * (b + l - i + 1), prec).div((i - b + l) * i, prec)
801+
sum = BigDecimal(0)
802+
prod = BigDecimal(1)
803+
804+
((b-l)..(b+l)).to_a.each_slice(batch_size) do |batch_ks|
805+
# Calculate prod{x-k} in this batch
806+
batch_prod, prod_coef = _x_minus_k_prod_coef(batch_ks, xn, internal_xn_prec)
807+
808+
# Calculate coefficients of batch_prod/(x-k)
809+
batch_coef = [0] * batch_ks.size
810+
c_scale = 1r
811+
batch_ks.each do |k|
812+
c_scale = c_scale * (-b * (b + l - k + 1)) / ((k - b + l) * k) if k != b - l
813+
rem = 0
814+
(batch_ks.size - 1).downto(0) do |i|
815+
quo = prod_coef[i + 1] + rem
816+
rem = quo * k
817+
batch_coef[i] += c_scale * quo
818+
end
767819
end
768-
sum = sum.add(c.div(x - i, prec), prec)
820+
821+
batch_sum = BigDecimal(0)
822+
batch_coef.each_with_index do |coef, i|
823+
batch_sum = batch_sum.add(xn[i].mult(coef.numerator, internal_xn_prec).div(coef.denominator, internal_xn_prec), internal_xn_prec)
824+
end
825+
sum = sum.add(batch_sum.mult(c, prec).div(batch_prod, prec), prec)
826+
c = c.mult(c_scale.numerator, prec).div(c_scale.denominator, prec)
827+
prod = prod.mult(batch_prod, prec)
828+
end
829+
830+
# Perform shift.times {|i| prod = prod.mult(x - i, prec) } with batch processing
831+
shift.times.to_a.each_slice(batch_size) do |batch_ks|
832+
shift_prod, _prod_coef = _x_minus_k_prod_coef(batch_ks, xn, internal_xn_prec)
833+
prod = prod.mult(shift_prod, prec)
769834
end
770835
else
771836
# Binary splitting
837+
prods = ((b-l)..(b+l)).map {|i| x - i } + shift.times.map {|i| x - i }
838+
prods = prods.each_slice(2).map {|a, b| b ? a.mult(b, prec) : a } while prods.size != 1
839+
prod = prods.first
772840
fractions = (b - l + 1..b + l).map do |i|
773841
denominator = (x - i).mult((i - b + l) * i, prec)
774842
numerator = (x - i + 1).mult(-b * (b + l - i + 1), prec)

0 commit comments

Comments
 (0)