Home > Software engineering >  Unexpected integer division vs. floating-point division result in Python
Unexpected integer division vs. floating-point division result in Python

Time:11-28

Running the following code in Python produces a somewhat unexpected result.

print(10000 // 0.1)  # prints 99999.0
print(10000 / 0.1)  # prints 100000.0

Now, I might have understood the discrepancy if both results were the same, because of how floating point numbers are stored in binary. The question is why is the second result different from the first one? Is there a difference in how / and // work besides the latter "flooring" the result?

CodePudding user response:

// computes the floor of the exact result of the division. / computes the nearest representable float to the exact result of the division.

For dividend 10000 (exactly 10000) and divisor 0.1 (slightly higher than the real number 0.1 due to floating-point limitations), the exact result is very slightly lower than 100000, but the difference is slight enough that the nearest representable float is 100000.0.

// produces 99999.0 because the exact result is less than 100000, but / produces 100000.0 rather than something like 99999.99999999999 because 100000.0 is the closest float to the exact result.

CodePudding user response:

TLDR; The difference is due both to the inexact floating point representation of the numbers, and the implementation of Cython's // being a little different than you might expect.

// is __floordiv__. __floordiv__(x, y) is supposed to be the same as floor(x / y). But you have already found out its not:

>>> floor(1.0 / 0.1)
10
>>> 1.0 // 0.1
9.0

In my opinion then, you are right to say this is unexpected behaviour. But why does this happen?

If you are using Cython then you can see what // does by reading the C code here. A simple Python implementation of that function that ignores a lot of extra details might look like this:

def myfloordiv(x, y):
    mod = x % y
    div = (x - mod) / y
    return float(floor(div))

So this is what x // y is doing, not simply floor(x / y). But in the case we are talking about, where x is a multiple of y, you might expect that mod here will be 0, so that div == x / y and the whole thing reduces down to what we actually want to do which is floor(x / y). However:

>>> 1.0 % 0.1
0.09999999999999995

So, the unexpected result comes in when doing the modulo operation, which is in the end, handled by the C standard library function fmod.

The reason fmod gets it wrong is most likely due to errors in floating point representation and/or arithmetic. I can illustrate this by picking a few other examples for you, all of which work as you would expect:

>>> 100.0 % 0.25
0.0
>>> 100.0 % 0.5
0.0
>>> 100.0 % 1.0
0.0
>>> 100.0 % 2.0
0.0
>>> 100.0 % 4.0
0.0

The pattern is, of course, that all the demonimators are powers of 2, so are representable exactly as floating point numbers, which suggests the errors in the % results are down to floating point respresentation.

I still think this behaviour is unexpected. An implementation of x // y that simply did floor(x, y) would be better as far as I can see. However, there is likely some edge cases or technical details the implementers had in mind when writing // that I am oblivious to.

  • Related