This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Rounding mode of floating-point division is not well documented
Type: behavior Stage:
Components: Interpreter Core Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: mark.dickinson, pitrou, serhiy.storchaka, tim.peters, vstinner
Priority: normal Keywords:

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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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) * (Python committer) 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:21adminsetgithub: 69316
2015-09-18 13:31:00vstinnersettitle: 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:45vstinnersettitle: suboptimal floating-point floor division -> Rounding mode of floating-point floor division is not well documented
2015-09-16 08:41:05mark.dickinsonsetmessages: + msg250830
2015-09-16 07:52:05vstinnersetmessages: + msg250827
2015-09-16 07:49:51pitrousetmessages: + msg250826
2015-09-16 07:41:41serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg250825
2015-09-16 07:27:49mark.dickinsonsetmessages: + msg250823
2015-09-16 02:10:52tim.peterssetmessages: + msg250815
2015-09-15 20:09:15tim.peterssetmessages: + msg250801
2015-09-15 20:07:45vstinnersetnosy: + vstinner
messages: + msg250800
2015-09-15 20:05:38pitrousetmessages: + msg250799
2015-09-15 19:24:37tim.peterssetmessages: + msg250797
2015-09-15 17:28:03pitroucreate