I was noticing that in some situations floor division (the // operator) seems to behave incorrectly. Here is one example:
>>> 59 // 0.2
294.0
Two things – one, the answer is just wrong. The correct answer is 295 (note that 59 / 0.2 gives the correct result of 295.0). The second issue is that this result is a float, not an integer.
I cannot for the life of me figure out how this is happening. Is it a bug? Some weird behavior when a float is in the divisor? I am using 3.12.
6
When you use 0.2
in source code, Python converts the decimal numeral 0.2 to floating-point. Python does not specify what floating-point format implementations use, but it is most commonly IEEE-754 binary64, called “double precision.”
The closest value representable in binary 64 to 0.2 is 0.200000000000000011102230246251565404236316680908203125.
So 59 // 0.2
divides 59 by 0.200000000000000011102230246251565404236316680908203125. The result before truncation is a little less than 295, so 294 is the correct result.
7
Stick to ints 😉
>>> divmod(59, .2)
(294.0, 0.19999999999999674)
That’s the heart of it. Floor and mod aren’t isolated operations in reality. After q, r = divmod(x, y)
, Python strives to get as close as it can to satisfying the mathematical identity x == q*y + r
, and
>>> 294 * .2 + 0.19999999999999674
59.0
The identity can be satisfied exactly when only ints are involved, but throw a float into the mix and it’s not always possible to meet all desirable outcomes simultaneously.
In this specific case, 59/.2
is not mathematically equal to 295, but to a real number slightly smaller than that (the float .2
is not one fifth):
>>> from decimal import Decimal as D
>>> D(59) / D(.2)
Decimal('294.9999999999999836242103868')
That’s why the floor is 294 – because that is the floor in reality.