classification
Title: Floor division is not the same as the floor of division
Type: behavior Stage: resolved
Components: Versions: Python 3.6, Python 3.2, Python 3.3, Python 3.4, Python 3.5
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: izaakweiss, mark.dickinson, r.david.murray, steven.daprano, tim.peters
Priority: normal Keywords:

Created on 2016-07-07 16:07 by izaakweiss, last changed 2016-07-07 16:31 by r.david.murray. This issue is now closed.

Messages (5)
msg269945 - (view) Author: Izaak Weiss (izaakweiss) Date: 2016-07-07 16:07
The floor division operator in Python 3 `x//y` acts differently than the floor of the division operator `math.floor(x/y)` in some edge cases due to floating point errors.

Consider `44//4.4` and `math.floor(44/4.4)`. These two expressions should, with infinite precision, evaluate to the same number. However, in practice `44//4.4` evaluates to `9.0`, but `math.floor(44/4.4)` evaluates to `10.0`.

This should either be changed, or made clear in the documentation that the two may not be equal due to floating point errors.
msg269947 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2016-07-07 16:23
The behaviour of both are correct: the binary float nearest to 4.4 is just a smidgen *bigger* than the exact decimal 4.4, so 44//4.4 truncates to 9.0. But floor(44/4.4) evaluates 44/4.4 first, and that rounds rather than truncating, giving 10.0, which then floors to 10.

If you want to propose a documentation patch, that will be considered, but keep in mind that the Python docs cannot and should not be the place to document all the hairy corners of floating point arithmetic.
msg269948 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-07-07 16:27
I see Steven beat me to posting, but I'll post this anyway since it addresses the existing documentation.

Those are already documented as being two different operations.  // is "floor" in the sense (as documented in https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations):

    x == (x//y) + (x%y)

or, more clearly,

    divmod(x, y) = (x//y, x%y)

math.floor(x/y), on the other hand, is doing the floating point division first, and then taking the floor.  Different operations, and thus, given floating point realities, different results are not surprising (at least to me...maybe I'm naive? :)

Is this worth an additional documentation note of some sort?  I'm not sure, I'll defer to Mark.
msg269949 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2016-07-07 16:29
Note that the same is true in Python 2.

I don't want to document it, though.  In `math.floor(44/4.4)`, the subexpression `44/4.4` by itself wholly rules out that "[as if] with infinite precision [throughout the larger expression]" may be in play.  `44/4.4` rounds to 10.0 regardless of the context it appears in.

It's just a universal fact about how float arithmetic works, and no more surprising than that, e.g., float addition isn't always associative.  Which may mean "very surprising" to many brand new to float arithmetic, but it's not the job of the Python docs to give basic tutorials.
msg269950 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-07-07 16:31
That's good enough for me.  Closing.
History
Date User Action Args
2016-07-07 16:31:57r.david.murraysetstatus: open -> closed
resolution: not a bug
messages: + msg269950

stage: resolved
2016-07-07 16:29:11tim.peterssetnosy: + tim.peters
messages: + msg269949
2016-07-07 16:27:54r.david.murraysetnosy: + r.david.murray, mark.dickinson
messages: + msg269948
2016-07-07 16:23:04steven.dapranosetnosy: + steven.daprano
messages: + msg269947
2016-07-07 16:07:27izaakweisscreate