3

To illustrate my example

Try running:

print(1//0.1)
print(1/0.1)
print(int(1/0.1))

Output:

>>> print(1//0.1)
9.0
>>> print(1/0.1)
10.0
>>> print(int(1/0.1))
10

The result of 1//0.1 is both not an integer, and incorrect. The same applies with other numbers / test-cases. Is this behavior documented anywhere in python? I could not find anything relevant.

iordanis
  • 1,284
  • 2
  • 15
  • 28
  • 3
    Apparently `//` is not actually integer division; it is what Python calls *floor division.* It is the result of performing the division and then *rounding down.* In floating point operations, this can cause some perverse effects; I suspect that 1/0.1 is actually something like 9.999999964, because many decimal numbers cannot be represented precisely as a power of two, which is what a floating point number is. Read [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) for more information. – Robert Harvey May 13 '22 at 20:41
  • Related: https://stackoverflow.com/questions/72052228/python-double-forward-slash-difference-from-floor – Ry- May 13 '22 at 21:14

1 Answers1

7

It's actually called __floordiv__ according to the data model. The docs for divmod mention the relation to math.floor:

[...] For floating point numbers the result is (q, a % b), where q is usually math.floor(a / b) but may be 1 less than that. In any case q * b + a % b is very close to a, [...]

Hence, if q, r = divmod(a, b) then it holds that q*b + r == a and q == a//b.

>>> a, b = 1, 0.1
>>> q, r = divmod(a, b)
>>> q*b + r == a
True
>>> a//b == q
True

So the only guarantee here is that q*b + r == a holds.


As for 1.0 // 0.1 yielding 9.0 it is because FP numbers are represented internally in base 2, and 0.1 is not a "round" number, but actually, larger than the "mathematical 0.1":

In [79]: f"{0.1:.20f}"
Out[79]: '0.10000000000000000555'
jsbueno
  • 99,910
  • 10
  • 151
  • 209
a_guest
  • 34,165
  • 12
  • 64
  • 118
  • 3
    @a_guest - I edited the text to add an example of what is going on with 0.1 to your excellent answer - it would not be worth to add another answer with just this snippet, and I think it should be more visible than a comment. Feel free to edit it back/change. – jsbueno May 13 '22 at 20:49
  • 1
    nitpick, this op is handled by `float.__rfloordiv__` not by `int.__floordiv__`. – wim May 13 '22 at 20:52
  • @jsbueno Thanks. Indeed, CPython chooses the mathematically correct way given that is has access to only the limited numerical precision that the numeral `0.1` has been converted to (at this point, Python does not have any information about the original numerals anymore). So it does its best by working with the numbers that the float objects correspond to. – a_guest May 13 '22 at 20:54