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:
parent
e56ba6231f
commit
f516379853
Notes:
git
2021-05-30 00:56:50 +09:00
3 changed files with 50 additions and 19 deletions
20
numeric.c
20
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue