diff options
author | Kenta Murata <3959+mrkn@users.noreply.github.com> | 2022-12-26 21:02:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-26 21:02:47 +0900 |
commit | 9f2378959e5c5b5c39c9993f1a84e5304ff113d6 (patch) | |
tree | be3582344869f036ed52b0c0a96e24d7c5655f9d | |
parent | bb60e4615f49a4dbc4f0eb776c8773feabb1a66f (diff) |
numeric.c: Fix round_half_even for specific values (#7023)
Handle the integert and the float parts separately in round_half_even
to prevent error occursions in floating point calculation.
Notes
Notes:
Merged-By: mrkn <mrkn@ruby-lang.org>
-rw-r--r-- | numeric.c | 26 | ||||
-rw-r--r-- | spec/ruby/core/float/round_spec.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_float.rb | 11 |
3 files changed, 28 insertions, 11 deletions
@@ -144,31 +144,37 @@ round_half_down(double x, double s) static double round_half_even(double x, double s) { - double f, d, xs = x * s; + double u, v, us, vs, f, d, uf; + + v = modf(x, &u); + us = u * s; + vs = v * s; if (x > 0.0) { - f = floor(xs); - d = xs - f; + f = floor(vs); + uf = us + f; + d = vs - f; if (d > 0.5) d = 1.0; - else if (d == 0.5 || ((double)((f + 0.5) / s) <= x)) - d = fmod(f, 2.0); + else if (d == 0.5 || ((double)((uf + 0.5) / s) <= x)) + d = fmod(uf, 2.0); else d = 0.0; x = f + d; } else if (x < 0.0) { - f = ceil(xs); - d = f - xs; + f = ceil(vs); + uf = us + f; + d = f - vs; if (d > 0.5) d = 1.0; - else if (d == 0.5 || ((double)((f - 0.5) / s) >= x)) - d = fmod(-f, 2.0); + else if (d == 0.5 || ((double)((uf - 0.5) / s) >= x)) + d = fmod(-uf, 2.0); else d = 0.0; x = f - d; } - return x; + return us + x; } static VALUE fix_lshift(long, unsigned long); diff --git a/spec/ruby/core/float/round_spec.rb b/spec/ruby/core/float/round_spec.rb index 50106025f9..e5a8f534e7 100644 --- a/spec/ruby/core/float/round_spec.rb +++ b/spec/ruby/core/float/round_spec.rb @@ -136,7 +136,7 @@ describe "Float#round" do -4.809999999999999.round(5, half: :even).should eql(-4.81) end - ruby_bug "", ""..."3.4" do + ruby_bug "", ""..."3.3" do # These numbers are neighbouring floating point numbers round a # precise value. They test that the rounding modes work correctly # round that value and precision is not lost which might cause diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index fdc5d28ed7..35cde1e951 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -486,6 +486,17 @@ class TestFloat < Test::Unit::TestCase assert_equal(-1.26, -1.255.round(2)) end + def test_round_half_even_with_precision + assert_equal(767573.18759, 767573.1875850001.round(5, half: :even)) + assert_equal(767573.18758, 767573.187585.round(5, half: :even)) + assert_equal(767573.18758, 767573.1875849998.round(5, half: :even)) + assert_equal(767573.18758, 767573.187575.round(5, half: :even)) + assert_equal(-767573.18759, -767573.1875850001.round(5, half: :even)) + assert_equal(-767573.18758, -767573.187585.round(5, half: :even)) + assert_equal(-767573.18758, -767573.1875849998.round(5, half: :even)) + assert_equal(-767573.18758, -767573.187575.round(5, half: :even)) + end + def test_floor_with_precision assert_equal(+0.0, +0.001.floor(1)) assert_equal(-0.1, -0.001.floor(1)) |