1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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]
This commit is contained in:
Jeremy Evans 2021-04-29 12:51:05 -07:00
parent e56ba6231f
commit f516379853
Notes: git 2021-05-30 00:56:50 +09:00
3 changed files with 50 additions and 19 deletions

View file

@ -2409,11 +2409,11 @@ ruby_float_step_size(double beg, double end, double unit, int excl)
if (unit == 0) { if (unit == 0) {
return HUGE_VAL; return HUGE_VAL;
} }
n= (end - beg)/unit;
err = (fabs(beg) + fabs(end) + fabs(end-beg)) / fabs(unit) * epsilon;
if (isinf(unit)) { if (isinf(unit)) {
return unit > 0 ? beg <= end : beg >= end; 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 (err>0.5) err=0.5;
if (excl) { if (excl) {
if (n<=0) return 0; if (n<=0) return 0;
@ -2421,10 +2421,26 @@ ruby_float_step_size(double beg, double end, double unit, int excl)
n = 0; n = 0;
else else
n = floor(n - err); 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 { else {
if (n<0) return 0; if (n<0) return 0;
n = floor(n + err); 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; return n+1;
} }

View file

@ -207,10 +207,12 @@ describe "Range#step" do
ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5]) ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
end end
it "returns Float values of 'step * n + begin < end'" do ruby_version_is '3.1' do
(1.0...6.4).step(1.8) { |x| ScratchPad << x } it "returns Float values of 'step * n + begin < end'" do
(1.0...55.6).step(18.2) { |x| ScratchPad << x } (1.0...6.4).step(1.8) { |x| ScratchPad << x }
ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 1.0, 19.2, 37.4]) (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 end
it "handles infinite values at either end" do 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 (-1.0...1.0).step(0.5).size.should == 4
end end
it "returns the range size when there's no step_size" do ruby_version_is '3.1' do
(-2..2).step.size.should == 5 it "returns the range size when there's no step_size" do
(-2.0..2.0).step.size.should == 5 (-2..2).step.size.should == 5
(-2..2.0).step.size.should == 5 (-2.0..2.0).step.size.should == 5
(-2.0..2).step.size.should == 5 (-2..2.0).step.size.should == 5
(1.0..6.4).step(1.8).size.should == 4 (-2.0..2).step.size.should == 5
(1.0..12.7).step(1.3).size.should == 10 (1.0..6.4).step(1.8).size.should == 4
(-2...2).step.size.should == 4 (1.0..12.7).step(1.3).size.should == 10
(-2.0...2.0).step.size.should == 4 (-2...2).step.size.should == 4
(-2...2.0).step.size.should == 4 (-2.0...2.0).step.size.should == 4
(-2.0...2).step.size.should == 4 (-2...2.0).step.size.should == 4
(1.0...6.4).step(1.8).size.should == 3 (-2.0...2).step.size.should == 4
(1.0...55.6).step(18.2).size.should == 3 (1.0...6.4).step(1.8).size.should == 3
(1.0...55.6).step(18.2).size.should == 4
end
end end
it "returns nil with begin and end are String" do it "returns nil with begin and end are String" do

View file

@ -882,6 +882,11 @@ class TestFloat < Test::Unit::TestCase
end end
assert_equal([5.0, 4.0, 3.0, 2.0], 5.0.step(1.5, -1).to_a) 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 end
def test_step2 def test_step2
@ -893,6 +898,7 @@ class TestFloat < Test::Unit::TestCase
a = rand a = rand
b = a+rand*1000 b = a+rand*1000
s = (b - a) / 10 s = (b - a) / 10
b = a + s*10
seq = (a...b).step(s) seq = (a...b).step(s)
assert_equal(10, seq.to_a.length, seq.inspect) assert_equal(10, seq.to_a.length, seq.inspect)
end end
@ -903,6 +909,11 @@ class TestFloat < Test::Unit::TestCase
(1.0 ... e).step(1E-16) do |n| (1.0 ... e).step(1E-16) do |n|
assert_operator(n, :<=, e) assert_operator(n, :<=, e)
end 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 end
def test_singleton_method def test_singleton_method