From: Yui NARUSE Date: 2011-09-21T11:27:06+09:00 Subject: [ruby-core:39649] [Ruby 1.9 - Bug #4576] Range#step miss the last value, if end-exclusive and has float number Issue #4576 has been updated by Yui NARUSE. I made a patch which fixes following 3 cases: * the error of loop count * duplicated values from underflow * excess of the end value diff --git a/ChangeLog b/ChangeLog index 0abb211..0f0c28b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Wed Sep 21 11:17:22 2011 NARUSE, Yui + + * numeric.c (ruby_float_step): improve floating point calculations. + [ruby-core:35753] [Bug #4576] + + * numeric.c (ruby_float_step): correct the error of floating point + numbers on the excluding case. + patched by Masahiro Tanaka [ruby-core:39608] + + * numeric.c (ruby_float_step): use the end value when the current + value is greater than or equal to the end value. + patched by Akira Tanaka [ruby-core:39612] + Tue Sep 20 18:08:51 2011 Nobuyoshi Nakada * vm_insnhelper.c (vm_get_cvar_base): reduce duplicated checks and diff --git a/numeric.c b/numeric.c index 18f5e1c..973da1f 100644 --- a/numeric.c +++ b/numeric.c @@ -1689,11 +1689,27 @@ ruby_float_step(VALUE from, VALUE to, VALUE step, int excl) if (unit > 0 ? beg <= end : beg >= end) rb_yield(DBL2NUM(beg)); } else { + double prev = beg == 0 ? -1 : 0; if (err>0.5) err=0.5; - n = floor(n + err); - if (!excl || ((long)n)*unit+beg < end) n++; + if (excl) { + if (n>0) { + if (n= end) { + if (!excl) rb_yield(DBL2NUM(end)); + break; + } + prev = d; + rb_yield(DBL2NUM(d)); } } return TRUE; diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index e77b9e6..4fc8a6b 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -508,4 +508,39 @@ class TestFloat < Test::Unit::TestCase sleep(0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1) end end + + def test_step + 1000.times do + a = rand + b = a+rand*1000 + s = (b - a) / 10 + assert_equal(11, (a..b).step(s).to_a.length) + end + + prev = 0 + (1.0..(1.0+1E-15)).step(1E-16) do |current| + assert_not_equal(prev, current) + prev = current + end + + (1.0..12.7).step(1.3).each do |n| + assert_operator(n, :<=, 12.7) + end + end + + def test_step_excl + 1000.times do + a = rand + b = a+rand*1000 + s = (b - a) / 10 + assert_equal(10, (a...b).step(s).to_a.length) + end + + assert_equal([1.0, 2.9, 4.8, 6.699999999999999], (1.0...6.8).step(1.9).to_a) + + e = 1+1E-12 + (1.0 ... e).step(1E-16) do |n| + assert_operator(n, :<, e) + end + end end ---------------------------------------- Bug #4576: Range#step miss the last value, if end-exclusive and has float number http://redmine.ruby-lang.org/issues/4576 Author: Joey Zhou Status: Open Priority: Normal Assignee: Category: core Target version: 1.9.4 ruby -v: - =begin Hi, I find that: * if: range.exclude_end? == true * and: any one in [begin_obj, end_obj, step] is a true Float(f.to_i != f) * and: unless begin_obj + step*int == end_obj * then: the result will miss the last value. for example: p (1...6.3).step.to_a # => [1.0, 2.0, 3.0, 4.0, 5.0], no 6.0 p (1.1...6).step.to_a # => [1.1, 2.1, 3.1, 4.1], no 5.1 p (1...6).step(1.1).to_a # => [1.0, 2.1, 3.2, 4.300000000000001], no 5.4 p (1.0...6.6).step(1.9).to_a # => [1.0, 2.9], no 4.8 p (1.0...6.7).step(1.9).to_a # => [1.0, 2.9, 4.8] p (1.0...6.8).step(1.9).to_a # => [1.0, 2.9, 4.8], no 6.7 Maybe the #step is ok on integers, but there's something wrong if the range is end-exclusive and contain float numbers. =end -- http://redmine.ruby-lang.org