Issue 25129: Rounding mode of floating-point division is not well documented (original) (raw)
Issue25129
Created on 2015-09-15 17:28 by pitrou, last changed 2022-04-11 14:58 by admin.
Messages (11) | ||
---|---|---|
msg250786 - (view) | Author: Antoine Pitrou (pitrou) * ![]() |
Date: 2015-09-15 17:28 |
>>> (78*6e-8) / 6e-8 78.0 >>> (78*6e-8) // 6e-8 77.0 Note this doesn't make divmod() wrong: >>> q, r = divmod(78*6e-8, 6e-8) >>> q, r (77.0, 5.999999999999965e-08) >>> r < 6e-8 True >>> q * 6e-8 + r == 78*6e-8 True But, still, it is somewhat of an oddity to not return the real quotient when it is an exact integer. Note this came from a Numpy issue where Numpy actually shows better behaviour: https://github.com/numpy/numpy/issues/6127 | ||
msg250797 - (view) | Author: Tim Peters (tim.peters) * ![]() |
Date: 2015-09-15 19:24 |
Stare at footnote 2 for the Reference Manual's "Binary arithmetic operations" section: """ [2] If x is very close to an exact integer multiple of y, it’s possible for x//y to be one larger than (x-x%y)//y due to rounding. In such cases, Python returns the latter result, in order to preserve that divmod(x,y)[0] * y + x % y be very close to x. """ This is such a case. >>> x 4.679999999999999e-06 >>> y 6e-08 >>> divmod(x,y)[0] * y + x % y == x True >>> (x/y) * y + x % y == x False >>> ((x/y) * y + x % y) / x # and not close at all 1.0128205128205128 Yes, trying to preserve identities in floating-point always was a fool's errand ;-) Note that "but x is an _exact_ integer multiple of y, not just 'very close to'" would be succumbing to an illusion: >>> dx = decimal.Decimal(x) >>> dy = decimal.Decimal(y) >>> dx / dy Decimal('77.99999999999999426488108630') It's just that x/y _appears_ to be an exact integer (78) due to rounding. As the decimal calcs show, infinite-precision floor division really would return 77. | ||
msg250799 - (view) | Author: Antoine Pitrou (pitrou) * ![]() |
Date: 2015-09-15 20:05 |
Hum, I see. What is the rounding mode used by true division, by the way? | ||
msg250800 - (view) | Author: STINNER Victor (vstinner) * ![]() |
Date: 2015-09-15 20:07 |
IEEE 754 uses the ROUND_HALF_EVEN rounding mode by default: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even That's why round() uses the same rounding mode. I discovered recently while working on the datetime module :-) (issue #23517) | ||
msg250801 - (view) | Author: Tim Peters (tim.peters) * ![]() |
Date: 2015-09-15 20:09 |
> What is the rounding mode used by true division, For binary floats? It inherits whatever the platform C's x/y double division uses. Should be nearest/even on "almost all" platforms now, unless the user fiddles with their FPU's rounding flags. | ||
msg250815 - (view) | Author: Tim Peters (tim.peters) * ![]() |
Date: 2015-09-16 02:10 |
BTW, I find this very hard to understand: "it’s possible for x//y to be one larger than" ... This footnote was written long before "//" was defined for floats. IIRC, the original version must have said something like: "it's possible for floor(x/y) to be one larger than" ... instead. Which does make sense ... yup, the old docs were actually coherent here ;-) https://docs.python.org/release/2.1/ref/binary.html | ||
msg250823 - (view) | Author: Mark Dickinson (mark.dickinson) * ![]() |
Date: 2015-09-16 07:27 |
I agree with Tim that this is the correct behaviour. Antoine: okay to close? Or should we leave it open for the doc fix that Tim noticed? | ||
msg250825 - (view) | Author: Serhiy Storchaka (serhiy.storchaka) * ![]() |
Date: 2015-09-16 07:41 |
>>> from fractions import Fraction >>> Fraction(78*6e-8) / Fraction(6e-8) Fraction(353610802237278976, 4533471823554859) >>> Fraction(78*6e-8) // Fraction(6e-8) 77 >>> float(Fraction(78*6e-8) / Fraction(6e-8)) 78.0 >>> Fraction(78*6e-8) / Fraction(6e-8) - 78 Fraction(-26, 4533471823554859) The root of the issue is that >>> Fraction(78*6e-8) != Fraction(78*6, 10**8) True >>> Fraction(6e-8) != Fraction(6, 10**8) True | ||
msg250826 - (view) | Author: Antoine Pitrou (pitrou) * ![]() |
Date: 2015-09-16 07:49 |
I have no problem with closing. Admittedly, I opened this issue mostly to get a witty and enlightening response :-) | ||
msg250827 - (view) | Author: STINNER Victor (vstinner) * ![]() |
Date: 2015-09-16 07:52 |
""" The root of the issue is that >>> Fraction(78*6e-8) != Fraction(78*6, 10**8) True >>> Fraction(6e-8) != Fraction(6, 10**8) True """ It's not an issue, it's just a fact: floating point rounding is annoying and very few people understand it :-) | ||
msg250830 - (view) | Author: Mark Dickinson (mark.dickinson) * ![]() |
Date: 2015-09-16 08:41 |
> What is the rounding mode used by true division, by the way? Probably not what you were asking, but with respect to true division of integers (int / int -> float), we always use round-half-to-even. Any deviation from that (non-IEEE 754 platforms excepted) is a bug that should be reported. |
History | |||
---|---|---|---|
Date | User | Action | Args |
2022-04-11 14:58:21 | admin | set | github: 69316 |
2015-09-18 13:31:00 | vstinner | set | title: Rounding mode of floating-point floor division is not well documented -> Rounding mode of floating-point division is not well documented |
2015-09-18 13:30:45 | vstinner | set | title: suboptimal floating-point floor division -> Rounding mode of floating-point floor division is not well documented |
2015-09-16 08:41:05 | mark.dickinson | set | messages: + |
2015-09-16 07:52:05 | vstinner | set | messages: + |
2015-09-16 07:49:51 | pitrou | set | messages: + |
2015-09-16 07:41:41 | serhiy.storchaka | set | nosy: + serhiy.storchakamessages: + |
2015-09-16 07:27:49 | mark.dickinson | set | messages: + |
2015-09-16 02:10:52 | tim.peters | set | messages: + |
2015-09-15 20:09:15 | tim.peters | set | messages: + |
2015-09-15 20:07:45 | vstinner | set | nosy: + vstinnermessages: + |
2015-09-15 20:05:38 | pitrou | set | messages: + |
2015-09-15 19:24:37 | tim.peters | set | messages: + |
2015-09-15 17:28:03 | pitrou | create |