[前][次][番号順一覧][スレッド一覧]

ruby-changes:67065

From: Jeremy <ko1@a...>
Date: Sat, 7 Aug 2021 07:04:10 +0900 (JST)
Subject: [ruby-changes:67065] d16b68cb22 (master): Use Rational for Float#round with ndigits > 14

https://git.ruby-lang.org/ruby.git/commit/?id=d16b68cb22

From d16b68cb2204eeb5af8bd39149202b630374c67f Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Mon, 26 Jul 2021 11:20:27 -0700
Subject: Use Rational for Float#round with ndigits > 14

ndigits higher than 14 can result in values that are slightly too
large due to floating point limitations.  Converting to rational
for the calculation and then back to float fixes these issues.

Fixes [Bug #14635]
Fixes [Bug #17183]

Co-authored by: Yusuke Endoh <mame@r...>
---
 internal/rational.h       | 1 +
 numeric.c                 | 4 ++++
 rational.c                | 6 ++++++
 test/ruby/test_numeric.rb | 8 ++++++++
 4 files changed, 19 insertions(+)

diff --git a/internal/rational.h b/internal/rational.h
index 6bbd2a9..a9e9674 100644
--- a/internal/rational.h
+++ b/internal/rational.h
@@ -39,6 +39,7 @@ VALUE rb_rational_cmp(VALUE self, VALUE other); https://github.com/ruby/ruby/blob/trunk/internal/rational.h#L39
 VALUE rb_rational_pow(VALUE self, VALUE other);
 VALUE rb_rational_floor(VALUE self, int ndigits);
 VALUE rb_numeric_quo(VALUE x, VALUE y);
+VALUE rb_flo_round_by_rational(int argc, VALUE *argv, VALUE num);
 VALUE rb_float_numerator(VALUE x);
 VALUE rb_float_denominator(VALUE x);
 
diff --git a/numeric.c b/numeric.c
index 5f7c162..a1801f9 100644
--- a/numeric.c
+++ b/numeric.c
@@ -2230,6 +2230,10 @@ flo_round(int argc, VALUE *argv, VALUE num) https://github.com/ruby/ruby/blob/trunk/numeric.c#L2230
 	frexp(number, &binexp);
 	if (float_round_overflow(ndigits, binexp)) return num;
 	if (float_round_underflow(ndigits, binexp)) return DBL2NUM(0);
+        if (ndigits > 14) {
+            /* In this case, pow(10, ndigits) may not be accurate. */
+            return rb_flo_round_by_rational(argc, argv, num);
+        }
 	f = pow(10, ndigits);
 	x = ROUND_CALL(mode, round, (number, f));
 	return DBL2NUM(x / f);
diff --git a/rational.c b/rational.c
index 76a4264..7324f78 100644
--- a/rational.c
+++ b/rational.c
@@ -1540,6 +1540,12 @@ nurat_round_n(int argc, VALUE *argv, VALUE self) https://github.com/ruby/ruby/blob/trunk/rational.c#L1540
     return f_round_common(argc, argv, self, round_func);
 }
 
+VALUE
+rb_flo_round_by_rational(int argc, VALUE *argv, VALUE num)
+{
+    return nurat_to_f(nurat_round_n(argc, argv, float_to_r(num)));
+}
+
 static double
 nurat_to_double(VALUE self)
 {
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index b5486d3..0593cb5 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -200,6 +200,14 @@ class TestNumeric < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_numeric.rb#L200
     assert_nil(a <=> :foo)
   end
 
+  def test_float_round_ndigits
+    bug14635 = "[ruby-core:86323]"
+    f = 0.5
+    31.times do |i|
+      assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})")
+    end
+  end
+
   def test_floor_ceil_round_truncate
     a = Class.new(Numeric) do
       def to_f; 1.5; end
-- 
cgit v1.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]