[ruby-core:126395] [Ruby Bug#22246] Rational#to.f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum
Issue #22246 has been reported by AndrewDile (Andrew Dile). ---------------------------------------- Bug #22246: Rational#to.f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by nobu (Nobuyoshi Nakada). ```console $ ruby -e 'x = 65803600513127829623; [65803600.51312783, 65803600.513127826].each {|n| p ((n*10**12).to_i-x).abs}' 3977 4215 ``` Isn't the current result closer? ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118603 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by AndrewDile (Andrew Dile). In your code example, the multiplication of n by 10**12 uses floating point arithmetic, which introduces a rounding error. Instead, it needs to convert n to an exact rational number before doing the multiplication. ```ruby x = 65803600513127829623; [65803600.51312783, 65803600.513127826].each {|n| p ((n.to_r*10**12).to_i-x).abs} 3981 3469 ``` ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118611 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by byroot (Jean Boussier). [Feature #21308] was filed a while ago. `dtoa.c` is really showing its age, and in the last couple years there has been a lot of new algorithms on that particular topic. Currently `ruby/json` uses Grisu for `double -> string` and Eisel-Lemire fast float for `string -> double`. If these benchmarks are to be believed: https://fmtlib.github.io/dtoa-benchmark/results/, `uscale` while much simpler than alternatives, may not be the best choice. If we're to migrate away from `dtoa.c` I think we might as well consider several alternatives rather than jump straight to `uscale`. So perhaps it'd be best to put [Feature #21308] on the next developer meeting topic to see what the criterias would be for replacement. ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118673 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). This bug is not related to Feature #21308 except for the same author. The implementation of Rational#to_f does not perform any base conversions. The bug is caused by the assumption that a fixnum denominator can be exactly represented by a double. Since fixnums have more than 53 bits on 64-bit systems, this assumption is false, and the result is sometimes off, as the example above shows. The relevant code in rb_big_fdiv_double is this: ``` if (FIXNUM_P(y)) { dy = (double)FIX2LONG(y); ``` It ought instead to check if y can be exactly represented by a double. ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118682 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). Perhaps this would suffice: ``` if (FIXNUM_P(y) && FIX2LONG(y) == (long)(double)FIX2LONG(y)) ``` ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118683 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by byroot (Jean Boussier).
This bug is not related to Feature #21308 except for the same author.
Yes, I confused it with another ticket, that's why I deleted my comment, sorry. ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118684 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). Consider the related code in `fix_fdiv_double`, which demonstrates a check for the denominator y being a fixnum that's exactly represented by a double. ``` static double fix_fdiv_double(VALUE x, VALUE y) { if (FIXNUM_P(y)) { long iy = FIX2LONG(y); #if SIZEOF_LONG * CHAR_BIT > DBL_MANT_DIG if ((iy < 0 ? -iy : iy) >= (1L << DBL_MANT_DIG)) { return rb_big_fdiv_double(rb_int2big(FIX2LONG(x)), rb_int2big(iy)); } #endif return double_div_double(FIX2LONG(x), iy); } ``` N.B. A simple round-trip check would be simpler and handle all the cases without needing to know anything about mantissa length: `if (iy != (long)(double)iy)` ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118687 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). This might fix it: ``` double rb_big_fdiv_double(VALUE x, VALUE y) { if (FIXNUM_P(y)) { return big_fdiv_int(x, rb_int2big(FIX2LONG(y))); } else if (RB_BIGNUM_TYPE_P(y)) { return big_fdiv_int(x, y); } else if (RB_FLOAT_TYPE_P(y)) { double dy = RFLOAT_VALUE(y); if (isnan(dy)) return dy; return big_fdiv_float(x, y); } else { return NUM2DBL(rb_num_coerce_bin(x, y, idFdiv)); } } ``` ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118688 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). Here's an example of Rational#to_f giving an incorrect result when the numerator is a bignum and the denominator is a fixnum that can't be exactly represented as a double: `Rational(10**34, 10**17 + 9).to_f` should return 1.0e+17, but it returns 9.999999999999998e+16. `10**34` = 10000000000000000000000000000000000, but `(10**34).to_f.to_i` = 9999999999999999455752309870428160. `10**17 + 9` = 100000000000000009, but `(10**17 + 9).to_f.to_i` = 100000000000000016. Since rb_big_fdiv_double converts both numerator and denominator to doubles, it computes 9999999999999999455752309870428160 / 100000000000000016, which gives an incorrect answer. ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118695 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
Issue #22246 has been updated by rgburger (Robert Burger). I found two more cases where `rb_big_fdiv_double` produces an incorrectly rounded result. I added them to [pull request #18599](https://github.com/ruby/ruby/pull/18559). ```Ruby assert_equal(3.333333333333333e39, (10**40).fdiv(3), "Bug #22246") assert_equal(3.333333333333333e39, (10**40).fdiv(3.0), "Bug #22246") ``` ---------------------------------------- Bug #22246: Rational#to_f incorrectly rounds when the numerator is a bignum that cannot be represented exactly by a double, and the denominator is a fixnum https://bugs.ruby-lang.org/issues/22246#change-118726 * Author: AndrewDile (Andrew Dile) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` c Rational(65803600513127829623, 10**12).to_f ``` The bignum numerator cannot be exactly represented by a double, but the code in `rb_big_fdiv_double` rounds it to a double (`dx`) before dividing by the fixnum denominator of 10^12, resulting in `65803600.51312783`, instead of the correctly rounded value, `65803600.513127826`. The `isinf(dx)` test does not cover this case. Moreover, not all fixnums (in 64-bit systems) can be represented exactly as doubles. -- https://bugs.ruby-lang.org/
participants (4)
-
AndrewDile (Andrew Dile) -
byroot (Jean Boussier) -
nobu (Nobuyoshi Nakada) -
rgburger (Robert Burger)