Issue #4576 has been updated by Yui NARUSE.


I updated a patch.

> % ./ruby -e 'a = (1.0..12.7).step(1.3).to_a; p a.all? {|n| n <= 12.7 }, a.last'
> > false
> > 12.700000000000001

This issue has 3 options:
(1) ignore
(2) return end
(3) change step

In this thread, we don't ignore it, so (2) or (3).
On (3) its middle results are also changed but it seems hard to predict.
So I use (2) on this patch.


diff --git a/numeric.c b/numeric.c
index 6d3c143..37f91bc 100644
--- a/numeric.c
+++ b/numeric.c
@@ -1690,10 +1690,21 @@ ruby_float_step(VALUE from, VALUE to, VALUE step, int excl)
 	}
 	else {
 	    if (err>0.5) err=0.5;
-	    n = floor(n + err);
-	    if (!excl || ((long)n)*unit+beg < end) n++;
-	    for (i=0; i<n; i++) {
-		rb_yield(DBL2NUM(i*unit+beg));
+	    if (excl) {
+		if (n<=0) return TRUE;
+		if (n<1)
+		    n = 0;
+		else
+		    n = floor(n - err);
+	    }
+	    else {
+		if (n<0) return TRUE;
+		n = floor(n + err);
+	    }
+	    for (i=0; i<=n; i++) {
+		double d = i*unit+beg;
+		if (end < d) d = end;
+		rb_yield(DBL2NUM(d));
 	    }
 	}
 	return TRUE;
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index e77b9e6..d163848 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -508,4 +508,33 @@ 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
+
+    (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