summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2021-04-29 12:51:05 -0700
committerJeremy Evans <code@jeremyevans.net>2021-05-29 08:56:15 -0700
commitf516379853f36d143d820c55d5eeaa9fc410ef52 (patch)
treee5a9e14896dee5767d0d33834613bd56c590ce66
parente56ba6231f77dd0aa88a1ce737a342baafc884c7 (diff)
Fix Enumerator::ArithmeticSequence handling of float ranges
Depending on the float range, there could be an off-by-one error, where the last result that should be in the range was missed. Fix this by checking if the computed value for the expected value outside the range is still inside the range, and if so, increment the step size. Fixes [Bug #16612]
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/4434
-rw-r--r--numeric.c20
-rw-r--r--spec/ruby/core/range/step_spec.rb38
-rw-r--r--test/ruby/test_float.rb11
3 files changed, 50 insertions, 19 deletions
diff --git a/numeric.c b/numeric.c
index 17a401f7d7..d0538ca126 100644
--- a/numeric.c
+++ b/numeric.c
@@ -2409,11 +2409,11 @@ ruby_float_step_size(double beg, double end, double unit, int excl)
if (unit == 0) {
return HUGE_VAL;
}
- n= (end - beg)/unit;
- err = (fabs(beg) + fabs(end) + fabs(end-beg)) / fabs(unit) * epsilon;
if (isinf(unit)) {
return unit > 0 ? beg <= end : beg >= end;
}
+ n= (end - beg)/unit;
+ err = (fabs(beg) + fabs(end) + fabs(end-beg)) / fabs(unit) * epsilon;
if (err>0.5) err=0.5;
if (excl) {
if (n<=0) return 0;
@@ -2421,10 +2421,26 @@ ruby_float_step_size(double beg, double end, double unit, int excl)
n = 0;
else
n = floor(n - err);
+ if (beg < end) {
+ if ((n+1)*unit+beg < end)
+ n++;
+ }
+ else if (beg > end) {
+ if ((n+1)*unit+beg > end)
+ n++;
+ }
}
else {
if (n<0) return 0;
n = floor(n + err);
+ if (beg < end) {
+ if ((n+1)*unit+beg <= end)
+ n++;
+ }
+ else if (beg > end) {
+ if ((n+1)*unit+beg >= end)
+ n++;
+ }
}
return n+1;
}
diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb
index e284551ab3..1c2e30742c 100644
--- a/spec/ruby/core/range/step_spec.rb
+++ b/spec/ruby/core/range/step_spec.rb
@@ -207,10 +207,12 @@ describe "Range#step" do
ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
end
- it "returns Float values of 'step * n + begin < end'" do
- (1.0...6.4).step(1.8) { |x| ScratchPad << x }
- (1.0...55.6).step(18.2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 1.0, 19.2, 37.4])
+ ruby_version_is '3.1' do
+ it "returns Float values of 'step * n + begin < end'" do
+ (1.0...6.4).step(1.8) { |x| ScratchPad << x }
+ (1.0...55.6).step(18.2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 1.0, 19.2, 37.4, 55.599999999999994])
+ end
end
it "handles infinite values at either end" do
@@ -457,19 +459,21 @@ describe "Range#step" do
(-1.0...1.0).step(0.5).size.should == 4
end
- it "returns the range size when there's no step_size" do
- (-2..2).step.size.should == 5
- (-2.0..2.0).step.size.should == 5
- (-2..2.0).step.size.should == 5
- (-2.0..2).step.size.should == 5
- (1.0..6.4).step(1.8).size.should == 4
- (1.0..12.7).step(1.3).size.should == 10
- (-2...2).step.size.should == 4
- (-2.0...2.0).step.size.should == 4
- (-2...2.0).step.size.should == 4
- (-2.0...2).step.size.should == 4
- (1.0...6.4).step(1.8).size.should == 3
- (1.0...55.6).step(18.2).size.should == 3
+ ruby_version_is '3.1' do
+ it "returns the range size when there's no step_size" do
+ (-2..2).step.size.should == 5
+ (-2.0..2.0).step.size.should == 5
+ (-2..2.0).step.size.should == 5
+ (-2.0..2).step.size.should == 5
+ (1.0..6.4).step(1.8).size.should == 4
+ (1.0..12.7).step(1.3).size.should == 10
+ (-2...2).step.size.should == 4
+ (-2.0...2.0).step.size.should == 4
+ (-2...2.0).step.size.should == 4
+ (-2.0...2).step.size.should == 4
+ (1.0...6.4).step(1.8).size.should == 3
+ (1.0...55.6).step(18.2).size.should == 4
+ end
end
it "returns nil with begin and end are String" do
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index fbf0d87f8e..ca616456f1 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -882,6 +882,11 @@ class TestFloat < Test::Unit::TestCase
end
assert_equal([5.0, 4.0, 3.0, 2.0], 5.0.step(1.5, -1).to_a)
+
+ assert_equal(11, ((0.24901079128550474)..(340.2500808898068)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((0.24901079128550474)..(340.25008088980684)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)..(-340.2500808898068)).step(-34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)..(-340.25008088980684)).step(-34.00010700985213).to_a.size)
end
def test_step2
@@ -893,6 +898,7 @@ class TestFloat < Test::Unit::TestCase
a = rand
b = a+rand*1000
s = (b - a) / 10
+ b = a + s*10
seq = (a...b).step(s)
assert_equal(10, seq.to_a.length, seq.inspect)
end
@@ -903,6 +909,11 @@ class TestFloat < Test::Unit::TestCase
(1.0 ... e).step(1E-16) do |n|
assert_operator(n, :<=, e)
end
+
+ assert_equal(10, ((0.24901079128550474)...(340.2500808898068)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((0.24901079128550474)...(340.25008088980684)).step(34.00010700985213).to_a.size)
+ assert_equal(10, ((-0.24901079128550474)...(-340.2500808898068)).step(-34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)...(-340.25008088980684)).step(-34.00010700985213).to_a.size)
end
def test_singleton_method