23

I have been reading about division and integer division in Python and the differences between division in Python2 vs Python3. For the most part it all makes sense. Python 2 uses integer division only when both values are integers. Python 3 always performs true division. Python 2.2+ introduced the // operator for integer division.

Examples other programmers have offered work out nice and neat, such as:

>>> 1.0 // 2.0      # floors result, returns float
0.0
>>> -1 // 2         # negatives are still floored
-1

How is // implemented? Why does the following happen:

>>> import math
>>> x = 0.5 
>>> y = 0.1
>>> x / y
5.0
>>> math.floor(x/y)
5.0
>>> x // y
4.0

Shouldn't x // y = math.floor(x/y)? These results were produced on python2.7, but since x and y are both floats the results should be the same on python3+. If there is some floating point error where x/y is actually 4.999999999999999 and math.floor(4.999999999999999) == 4.0 wouldn't that be reflected in x/y?

The following similar cases, however, aren't affected:

>>> (.5*10) // (.1*10)
5.0
>>> .1 // .1
1.0
None
  • 5,491
  • 1
  • 40
  • 51

4 Answers4

24

I didn't find the other answers satisfying. Sure, .1 has no finite binary expansion, so our hunch is that representation error is the culprit. But that hunch alone doesn't really explain why math.floor(.5/.1) yields 5.0 while .5 // .1 yields 4.0.

The punchline is that a // b is actually doing floor((a - (a % b))/b), as opposed to simply floor(a/b).

.5 / .1 is exactly 5.0

First of all, note that the result of .5 / .1 is exactly 5.0 in Python. This is the case even though .1 cannot be exactly represented. Take this code, for instance:

from decimal import Decimal

num = Decimal(.5)
den = Decimal(.1)
res = Decimal(.5/.1)

print('num: ', num)
print('den: ', den)
print('res: ', res)

And the corresponding output:

num:  0.5
den:  0.1000000000000000055511151231257827021181583404541015625
res:  5

This shows that .5 can be represented with a finite binary expansion, but .1 cannot. But it also shows that despite this, the result of .5 / .1 is exactly 5.0. This is because floating point division results in the loss of precision, and the amount by which den differs from .1 is lost in the process.

That's why math.floor(.5 / .1) works as you might expect: since .5 / .1 is 5.0, writing math.floor(.5 / .1) is just the same as writing math.floor(5.0).

So why doesn't .5 // .1 result in 5?

One might assume that .5 // .1 is shorthand for floor(.5 / .1), but this is not the case. As it turns out, the semantics differ. This is even though the PEP says:

Floor division will be implemented in all the Python numeric types, and will have the semantics of

    a // b == floor(a/b)

As it turns out, the semantics of .5 // .1 are actually equivalent to:

floor((.5 - mod(.5, .1)) / .1)

where mod is the floating point remainder of .5 / .1 rounded towards zero. This is made clear by reading the Python source code.

This is where the fact that .1 can't be exactly represented by binary expansion causes the problem. The floating point remainder of .5 / .1 is not zero:

>>> .5 % .1
0.09999999999999998

and it makes sense that it isn't. Since the binary expansion of .1 is ever-so-slightly greater than the actual decimal .1, the largest integer alpha such that alpha * .1 <= .5 (in our finite precision math) is alpha = 4. So mod(.5, .1) is nonzero, and is roughly .1. Hence floor((.5 - mod(.5, .1)) / .1) becomes floor((.5 - .1) / .1) becomes floor(.4 / .1) which equals 4.

And that's why .5 // .1 == 4.

Why does // do that?

The behavior of a // b may seem strange, but there's a reason for it's divergence from math.floor(a/b). In his blog on the history of Python, Guido writes:

The integer division operation (//) and its sibling, the modulo operation (%), go together and satisfy a nice mathematical relationship (all variables are integers):

a/b = q with remainder r

such that

b*q + r = a and 0 <= r < b

(assuming a and b are >= 0).

Now, Guido assumes that all variables are integers, but that relationship will still hold if a and b are floats, if q = a // b. If q = math.floor(a/b) the relationship won't hold in general. And so // might be preferred because it satisfies this nice mathematical relationship.

jme
  • 19,895
  • 6
  • 41
  • 39
  • Would you say that this qualifies as a bug in the interpreter, given the contradiction with the PEP? – zwol Aug 20 '15 at 19:41
  • 1
    @zwol Rather than a bug in the interpreter, I'd say it's a bug in the PEP :). There are reasons to prefer `a // b` to `math.floor(a, b)`, and I can't see where the documentation acknowledges this, and the PEP's statement about `//`'s semantics seems to be clearly incorrect. I've added a blurb about this in the answer, if you're interested. – jme Aug 20 '15 at 20:02
  • 1
    `.5 // .1` is not equivalent to `floor((.5 - mod(.5, .1)) / .1)`; when you read the source, you missed the `if (div - floordiv > 0.5) floordiv += 1.0;` part directly after the `floordiv = floor(div);` part. This means that it rounds instead of flooring. – user2357112 Aug 21 '15 at 04:20
  • 1
    It's also important to note that `4.0` is the *correctly rounded* result - that is, if you took the real numbers represented by the two floating-point inputs `.5` and `.1` (not the real numbers .5 and .1), applied the ideal mathematical function f(x, y) = floor(x/y) with no intermediate rounding, and then chose as your return value the floating-point number closest to the real number result, `4.0` is what you would get. I haven't analyzed whether the floordiv implementation produces correctly-rounded results more frequently than applying floating-point division and floating-point floor... – user2357112 Aug 21 '15 at 04:25
  • in sequence, and anyway, the relationship between `//` and `%` is likely more important to preserve. – user2357112 Aug 21 '15 at 04:28
  • @user2357112 "when you read the source, you missed the..." Yes, I omitted this because in the particular case of `.5 / .1`, the rounding branch of the `if` is not followed, but you're right that in general the result is rounded instead of floored. I'll update my answer to clarify. Thanks! – jme Aug 22 '15 at 00:10
6

That's because

>>> .1
0.10000000000000001

.1 cannot be precisely represented in binary

You can also see that

>>> .5 / 0.10000000000000001
5.0
njzk2
  • 38,969
  • 7
  • 69
  • 107
  • I thought that, but my interpreter seems to hide it: `>>> 0.1` outputs `0.1` – None Aug 20 '15 at 16:44
  • 1
    @J.Money: try `(0.1).as_integer_ratio()` or `format(0.1, '1.30f')` – DSM Aug 20 '15 at 16:49
  • Thanks @DSM, both of those illustrate it. – None Aug 20 '15 at 16:54
  • 1
    This should not be the accepted answer. I guess technically it answers the OP's question, but there is no explanation. Please update your answer with a link such as the following to make it more complete: http://www.h-schmidt.net/FloatConverter/IEEE754.html, http://grouper.ieee.org/groups/754/, https://en.wikipedia.org/wiki/IEEE_floating_point – searchengine27 Aug 20 '15 at 19:44
  • 1
    @searchengine27 `.1 cannot be precisely represented in binary` does that not explain sufficiently? (also, other answers have given different levels of details, and user5248483 posted a link to the python 3 tutorial on floats that does a great job at explaining all the subtleties) – njzk2 Aug 20 '15 at 19:51
  • 1
    not even close. Where are the formulas for floats? explanations of how things are actually represented? Saying "that doesn't work" is not equivalent to saying "this is how it works". Referencing IEEE 754 might be a start, but you haven't even made the effort to do that. – searchengine27 Aug 20 '15 at 19:52
  • 3
    Saying somebody else posted the right answer, does not make your answer right. It makes their answer right, and makes it more obvious that your answer is incomplete. Look, you can either sit here and cry about it in the comments because I called you out on it and you clearly take offense to that, or take the out I provided you and just fix your answer. EDIT: also, his answer is not complete either, because this is not a python specific problem. It affects any base 2 cpu (which I'm not aware of a non-base2 cpu) – searchengine27 Aug 20 '15 at 19:58
  • 1
    @searchengine27 please vote for the answer you think answers the question best (if none does, please consider providing an answer yourself). The *accepted answer* is purely the choice of the OP, no matter what your opinion is on the subject, *this is how it works* (as for why, and for explanation, I encourage you to go to meta for that) – njzk2 Aug 20 '15 at 20:04
  • @searchengine27 I don't follow. The python 3 floats tutorial explain briefly floating point arithmetic in general, and then in particular in the case of python. – njzk2 Aug 20 '15 at 20:07
  • This answer is not correct, see the top-rated answer. – BlueRaja - Danny Pflughoeft Aug 21 '15 at 01:20
  • 1
    ‐1 -- This answer is utterly incomplete and unsatisfying and as such not useful. – Bakuriu Aug 21 '15 at 05:57
2

The issue is that Python will round the output as described here. Since 0.1 cannot be represented exactly in binary, the result is something like 4.999999999999999722444243844000. Naturally this becomes 5.0 when not using format.

  • 1
    Excellent resource. That was the document I was looking for. – None Aug 20 '15 at 17:28
  • 1
    This isn't correct, I'm afraid. `.5 / .1` is `5.0` *exactly*. See: `(.5/.1).as_integer_ratio()`, which yields `(5,1)`. – jme Aug 20 '15 at 19:10
-1

This isn't correct, I'm afraid. .5 / .1 is 5.0 exactly. See: (.5/.1).as_integer_ratio(), which yields (5,1).

Yes, 5 can be represented as 5/1, that is true. But in order to see the fraction of the actual result Python gives due to the inexact representation, follow along.

First, import:

from decimal import *
from fractions import Fraction

Variables for easy usage:

// as_integer_ratio() returns a tuple
xa = Decimal((.5).as_integer_ratio()[0])
xb = Decimal((.5).as_integer_ratio()[1])
ya = Decimal((.1).as_integer_ratio()[0])
yb = Decimal((.1).as_integer_ratio()[1])

Yields the following values:

xa = 1
xb = 2
ya = 3602879701896397
yb = 36028797018963968

Naturally, 1/2 == 5 and 3602879701896397 / 36028797018963968 == 0.1000000000000000055511151231.

So what happens when we divide?

>>> print (xa/xb)/(ya/yb)
4.999999999999999722444243845

But when we want the integer ratio...

>>> print float((xa/xb)/(ya/yb)).as_integer_ratio()
(5, 1)

As stated earlier, 5 is 5/1 of course. That's where Fraction comes in:

>>> print Fraction((xa/xb)/(ya/yb))
999999999999999944488848769/200000000000000000000000000

And wolfram alpha confirms that this is indeed 4.999999999999999722444243845.


Why don't you just do Fraction(.5/.1) or Fraction(Decimal(.5)/Decimal(.1))?

The latter will give us the same 5/1 result. The former will give us 1249999999999999930611060961/250000000000000000000000000 instead. This results in 4.999999999999999722444243844, similar but not the same result.

  • 2
    You have to be careful here: `xa`, `xb`, `ya`, and `yb` are `Decimal` objects, not Python `float`s, so showing that `(xa/xb)/(ya/yb)` is `4.999...` merely demonstrates the arbitrary precision arithmetic that the `Decimal` class provides. If you write `Decimal(.5 / .1)` you'll see that `.5 / .1` is *exactly* `5.0` -- even though `.1` exhibits representation error, `.5 / .1` does *not*, because the error is lost in the division. – jme Aug 21 '15 at 00:54