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

Make Float#floor with ndigits argument handle error

The previous implementation could result in a returned
float that is 1/(10**ndigits) too low.  First try adding
one before dividing, and if that results in a value that is
greater than the initial number, then try the original
calculation.

Spec added for ciel, but the issue doesn't appear to affect
ciel, at least not for the same number.  If the issue does
effect ciel, a similar fix could probably work for it.

Fixes [Bug #18018]
This commit is contained in:
Jeremy Evans 2021-07-26 10:45:56 -07:00
parent 338b604b32
commit 35e467080c
Notes: git 2021-07-28 03:01:05 +09:00
2 changed files with 16 additions and 3 deletions

View file

@ -1829,20 +1829,24 @@ flo_prev_float(VALUE vx)
VALUE VALUE
rb_float_floor(VALUE num, int ndigits) rb_float_floor(VALUE num, int ndigits)
{ {
double number, f; double number;
number = RFLOAT_VALUE(num); number = RFLOAT_VALUE(num);
if (number == 0.0) { if (number == 0.0) {
return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0); return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0);
} }
if (ndigits > 0) { if (ndigits > 0) {
int binexp; int binexp;
double f, mul, res;
frexp(number, &binexp); frexp(number, &binexp);
if (float_round_overflow(ndigits, binexp)) return num; if (float_round_overflow(ndigits, binexp)) return num;
if (number > 0.0 && float_round_underflow(ndigits, binexp)) if (number > 0.0 && float_round_underflow(ndigits, binexp))
return DBL2NUM(0.0); return DBL2NUM(0.0);
f = pow(10, ndigits); f = pow(10, ndigits);
f = floor(number * f) / f; mul = floor(number * f);
return DBL2NUM(f); res = (mul + 1) / f;
if (res > number)
res = mul / f;
return DBL2NUM(res);
} }
else { else {
num = dbl2ival(floor(number)); num = dbl2ival(floor(number));

View file

@ -229,6 +229,15 @@ class TestNumeric < Test::Unit::TestCase
assert_equal(-1, a.truncate) assert_equal(-1, a.truncate)
end end
def test_floor_ceil_ndigits
bug17183 = "[ruby-core:100090]"
f = 291.4
31.times do |i|
assert_equal(291.4, f.floor(i+1), bug17183)
assert_equal(291.4, f.ceil(i+1), bug17183)
end
end
def assert_step(expected, (from, *args), inf: false) def assert_step(expected, (from, *args), inf: false)
kw = args.last.is_a?(Hash) ? args.pop : {} kw = args.last.is_a?(Hash) ? args.pop : {}
enum = from.step(*args, **kw) enum = from.step(*args, **kw)