Skip to content

Commit 5c6854b

Browse files
authored
Fix inaccurate calculation (last digit) and add a workaround for add/sub hang bug (#465)
1 parent 3ce346c commit 5c6854b

File tree

3 files changed

+27
-9
lines changed

3 files changed

+27
-9
lines changed

lib/bigdecimal/math.rb

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -563,13 +563,23 @@ def expm1(x, prec)
563563
if x < -1
564564
# log10(exp(x)) = x * log10(e)
565565
lg_e = 0.4342944819032518
566-
exp_prec = prec + (lg_e * x).ceil + 2
566+
exp_prec = prec + (lg_e * x).ceil + BigDecimal.double_fig
567567
elsif x < 1
568-
exp_prec = prec - x.exponent + 2
568+
exp_prec = prec - x.exponent + BigDecimal.double_fig
569569
else
570570
exp_prec = prec
571571
end
572-
exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1)
572+
573+
return BigDecimal(-1) if exp_prec <= 0
574+
575+
exp = exp(x, exp_prec)
576+
577+
if exp.exponent > prec + BigDecimal.double_fig
578+
# Workaroudn for https://github.com/ruby/bigdecimal/issues/464
579+
exp
580+
else
581+
exp.sub(1, prec)
582+
end
573583
end
574584

575585
# erf(decimal, numeric) -> BigDecimal
@@ -596,7 +606,12 @@ def erf(x, prec)
596606
log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5)
597607
erfc_prec = [prec + log10_erfc.ceil, 1].max
598608
erfc = _erfc_asymptotic(x, erfc_prec)
599-
return BigDecimal(1).sub(erfc, prec) if erfc
609+
if erfc
610+
# Workaround for https://github.com/ruby/bigdecimal/issues/464
611+
return BigDecimal(1) if erfc.exponent < -prec - BigDecimal.double_fig
612+
613+
return BigDecimal(1).sub(erfc, prec)
614+
end
600615
end
601616

602617
prec2 = prec + BigDecimal.double_fig
@@ -623,7 +638,7 @@ def erfc(x, prec)
623638
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc)
624639
return BigDecimal::Internal.nan_computation_result if x.nan?
625640
return BigDecimal(1 - x.infinite?) if x.infinite?
626-
return BigDecimal(1).sub(erf(x, prec), prec) if x < 0
641+
return BigDecimal(1).sub(erf(x, prec + BigDecimal.double_fig), prec) if x < 0
627642
return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow)
628643

629644
if x >= 8
@@ -714,12 +729,12 @@ def gamma(x, prec)
714729
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma)
715730
prec2 = prec + BigDecimal.double_fig
716731
if x < 0.5
717-
raise Math::DomainError 'Numerical argument is out of domain - gamma' if x.frac.zero?
732+
raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero?
718733

719734
# Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z)
720735
pi = PI(prec2)
721736
sin = _sinpix(x, pi, prec2)
722-
return pi.div(gamma(1 - x, prec).mult(sin, prec2), prec)
737+
return pi.div(gamma(1 - x, prec2).mult(sin, prec2), prec)
723738
elsif x.frac.zero? && x < 1000 * prec
724739
return _gamma_positive_integer(x, prec2).mult(1, prec)
725740
end
@@ -747,7 +762,7 @@ def lgamma(x, prec)
747762
# Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z)
748763
pi = PI(prec2)
749764
sin = _sinpix(x, pi, prec2)
750-
log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec).first + BigMath.log(sin.abs, prec2), prec)
765+
log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec)
751766
[log_gamma, sin > 0 ? 1 : -1]
752767
elsif x.frac.zero? && x < 1000 * prec
753768
log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec)

test/bigdecimal/helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def assert_converge_in_precision(&block)
5353
[50, 100, 150].each do |n|
5454
value = yield(n)
5555
assert(value != expected, "Unable to estimate precision for exact value")
56-
assert_in_exact_precision(expected, value, n)
56+
assert_equal(expected.mult(1, n), value)
5757
end
5858
end
5959

test/bigdecimal/test_bigmath.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ def test_expm1
476476
assert_in_exact_precision(exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100)
477477
assert_in_exact_precision(exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100)
478478
assert_in_exact_precision(exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100)
479+
assert_equal(exp(BigDecimal("1e+12"), N), expm1(BigDecimal("1e+12"), N))
479480
end
480481

481482
def test_erf
@@ -486,6 +487,7 @@ def test_erf
486487
assert_equal(-1, BigMath.erf(MINF, N))
487488
assert_equal(1, BigMath.erf(BigDecimal(1000), 100))
488489
assert_equal(-1, BigMath.erf(BigDecimal(-1000), 100))
490+
assert_equal(1, BigMath.erf(BigDecimal(10000000), 100))
489491
assert_not_equal(1, BigMath.erf(BigDecimal(10), 45))
490492
assert_not_equal(1, BigMath.erf(BigDecimal(15), 100))
491493
assert_equal(1, BigMath.erf(BigDecimal('1e400'), 10))
@@ -524,6 +526,7 @@ def test_erfc
524526
BigMath.erfc(BigDecimal("40"), 100)
525527
)
526528
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(30), n) }
529+
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(-2), n) }
527530
assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) }
528531
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) }
529532
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) }

0 commit comments

Comments
 (0)