From 9ca16187a63f73c9faff8144a5d3f6a656f7d24f Mon Sep 17 00:00:00 2001 From: boulea7 Date: Sun, 4 Oct 2026 08:09:18 +0800 Subject: [PATCH] Preserve Rational precision near one in BigMath.log Change-Id: I7b0a9b59474014d587253e73eeb9106a86caa00a --- lib/bigdecimal.rb | 9 +++++- test/bigdecimal/test_bigdecimal.rb | 44 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 5b492ec0..89b82f95 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -304,7 +304,14 @@ 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) + if Rational === x && x.between?(1/2r, 2r) + # Preserve the significant digits of the difference from one. + input_prec = [prec + BigDecimal::Internal::EXTRA_PREC, 2 * BigDecimal.double_fig].max + difference = BigDecimal(x - 1, input_prec) + x = difference.add(1, input_prec + [1 - difference.exponent, 0].max) + else + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log) + end 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? diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a24bdbcc..5db974e9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2446,6 +2446,7 @@ def test_BigMath_log_with_1 assert_in_delta(0.0, BigMath.log(1, 20)) assert_in_delta(0.0, BigMath.log(1.0, 20)) assert_in_delta(0.0, BigMath.log(BigDecimal(1), 20)) + assert_positive_zero(BigMath.log(1r, 20)) end def test_BigMath_log_with_exp_1 @@ -2488,6 +2489,49 @@ def test_BigMath_log_with_rational assert_in_epsilon(BigMath.log(BigDecimal(3 / 7r, 100), 100), BigMath.log(3 / 7r, 100), 1e-99) end + def test_BigMath_log_with_rational_close_to_one + n = 12345**9 + # The first two and three terms of log(1 + 1/n)'s series round to this reference. + expected = BigDecimal("1.5016895257722442449503942510970658615421451954590e-37") + assert_in_delta(expected, BigMath.log(Rational(n + 1, n), 50), BigDecimal("1e-85")) + assert_in_delta(-expected, BigMath.log(Rational(n, n + 1), 50), BigDecimal("1e-85")) + end + + def test_BigMath_log_with_rational_close_to_one_at_low_precision + n = 12345**9 + assert_in_delta(BigDecimal("1.5017e-37"), BigMath.log(Rational(n + 1, n), 5), BigDecimal("1e-41")) + end + + def test_BigMath_log_with_rational_closer_to_one_than_precision + n = 10**100 + assert_in_delta(BigDecimal("1e-100"), BigMath.log(Rational(n + 1, n), 50), BigDecimal("1e-148")) + assert_in_delta(BigDecimal("-1e-100"), BigMath.log(Rational(n, n + 1), 50), BigDecimal("1e-148")) + end + + def test_BigMath_log_with_rational_close_to_one_with_limit + n = 12345**9 + expected = BigDecimal("1.5016895257722442449503942510970658615421451954590e-37") + BigDecimal.save_rounding_mode do + [:half_up, :down, :ceiling, :floor].each do |mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, mode) + upper, lower = BigDecimal.save_limit do + BigDecimal.limit(10) + results = [BigMath.log(Rational(n + 1, n), 50), BigMath.log(Rational(n, n + 1), 50)] + assert_equal(10, BigDecimal.limit) + results + end + assert_in_delta(expected, upper, BigDecimal("1e-85"), "rounding mode: #{mode}") + assert_in_delta(-expected, lower, BigDecimal("1e-85"), "rounding mode: #{mode}") + end + end + end + + def test_BigMath_log_with_exact_rationals + [1/4r, 1/2r, 3/2r, 2r, 3r].each do |x| + assert_equal(BigMath.log(BigDecimal(x, 50), 50), BigMath.log(x, 50)) + end + end + def test_BigMath_log_under_gc_stress paths = $LOAD_PATH.map{|path| "-I#{path}" } assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], [])