What is the result of this code: print(6 // 0.4)
I thought the answer is 15.0 but I am getting 14.0. Any explanation for this answer I will appreciate.
This is a really good question!
I think whatâs going on is that when 0.4 is converted to binary floating point, you get a value a tiny bit bigger than 0.4:
In [40]: f"{0.4:.40f}"
Out[40]: '0.4000000000000000222044604925031308084726'
So 6 / 0.4 is a tiny bit smaller than 15.0, and thus gets truncated to 14.0
However, (and now Iâm out of my depth with FP) â when you actually compute 6 / 0.4, you do get 15.0 (which is exactly representable in binary) as far as I can tell:
In [43]: f"{6 / 0.4:.60f}"
Out[43]: '15.000000000000000000000000000000000000000000000000000000000000'
So I donât get why the rounding doesnât occur the same way before the truncation.
Iâm sure someone that gets FP better than I do can explain âŚ
CHB
If we say x = 6
and y = 0.4
Then x / y
would be 15
, but //
is called floor division, so you get 14.0
, but what you donât see is the remainder, which as a ratio, is less than â0.4â. You can see both the quotient and the remainder by using a function called divmod()
:
divmod(x, y) = (14.0, 0.3999999999999997)
Perhaps a more convincing use case would be if you needed to return the number of days and hours for any given number of hours:
day = 24 # number of hours in a day
hours = 172 # number of given hours
days, hrs = divmod(hours, day)
print(f"{days} Days, {hrs} hrs")
print(hours / day)
print(hours // day)
Output:
7 Days, 4 hrs
7.166666666666667
7
Edit done for minor code correction.
There is no âinteger divisionâ in Python. We have floor division which does division, keeping the floor of the result.
In case you donât know what floor and ceiling are in maths:
Floor division with floats is weird, it is much easier to understand it with only integer arguments. 60//4
is exactly 15, as we expect.
As usual, the weirdness is because floats are binary numbers, not decimal. The float 0.4
is not exactly equal to the decimal number 0.4, it is actually a tiny bit larger:
>>> "%.50f" % 0.4
'0.40000000000000002220446049250313080847263336181641'
So weâre actually dividing by something a tiny bit bigger than the decimal 0.4. And that makes a difference.

In real numbers, 6 divided by exactly 0.4 gives exactly 15.

But something a tiny bit bigger than 0.4 only goes 14 times into 6, plus a reminder.
We can work out the remainder:
>>> 6 % 0.4
0.3999999999999997
Look at the remainder: it is almost equal to 0.4, in fact it is the very next float just under 0.4
:
>>> math.nextafter(0.4, 0.0)
0.39999999999999997
With true division, 6/0.4
, we get:
 6 divided by something a tiny bit bigger than 0.4 gives something a tiny bit less than 15;
 but in this case, the division rounds up to
15.0
exactly.
If you are thinking this is all terribly complicated, you are right.
What still confuses me here is this:
In [73]: f"{0.4:.40f}"
Out[73]: '0.4000000000000000222044604925031308084726'
OK, so the binary representation of the the literal 0.4 is a tiny bit larger than the real number 0.4 â got it. So then 6.0 / 0.4 would be expected to be a tiny bit smaller than 15.0:
In [75]: 6.0 / 0.4
Out[75]: 15.0
but a tiny bit smaller (as pointed out, as close as you can get in 64 bit float) would be displayed by python as a rounded 15.0.
However:
In [77]: f"{6.0 / 0.4:.40f}"
Out[77]: '15.0000000000000000000000000000000000000000'
so it seems to actually be 15.0, and exactly the same as what the literal 15.0 creates:
In [82]: (6.0 / 0.4) == 15.0
Out[82]: True
IIUC, small integers are exactly represented in FP â so it seems when we do the division we get exactly 15. So there seems to be some sort of rounding thatâs going on when we do the plain division thatâs not being done before the floor is taken in floor division â and that has me confused.
NOTE: the days, hours example reminded me of code I wrote years ago that converted latitude and longitude from decimal (float) values to degrees, minutes, seconds. The simple way, using % and the like, for certain values would result in things like:
9 degrees, 23 minutes 60 seconds
when that should be 24 minutes, zero seconds.
Sorry I donât remember the particulars at the moment, but for certain values, you had the issue that the FP value was a tiny bit under 60, so it didnât add another minute,but then rounded up to 60 for display.
I only caught it because I had arbitrarily chosen a number in a test that triggered the issue.
Yes, integers with absolute value <= 2**53 are represented exactly in IEEE754 double precision (CPythonâs float type).
6.0 / 0.4
is exactly 15.0 because hardware division rounds it up to 15. 15.0 is the closest representable value to the infinitely precise result.
But Pythonâs floor division doesnât care about that  itâs rounding down to the nearest integer in the direction of minus infinity. Hardware isnât doing it  CPython is working hard to give you the true floor. To infinite precision, 6 divided by the float approximation of 0.4 is a little less than 15.0, so flooring that gives 14.0.
The lovely mpmath
module lets you investigate things like this with ease, doing IEEEstyle binary arithmetic with usersettable precision:
>>> import mpmath
>>> print(mpmath.mp)
Mpmath settings:
mp.prec = 53 [default: 53] # by default, 53 bits, same as a 754 double
mp.dps = 15 [default: 15]
mp.trap_complex = False [default: False]
>>> mpmath.mp.prec = 80 # boost precision to 80 bits
>>> print(mpmath.mp)
Mpmath settings:
mp.prec = 80 [default: 53] # we changed this
mp.dps = 23 [default: 15]
mp.trap_complex = False [default: False]
>>> mpmath.mpf(0.4) # the number in question
mpf('0.40000000000000002220446049')
>>> 6.0 / mpmath.mpf(0.4) # showing that the infinitely precise result is < 15
mpf('14.999999999999999167332732')
>>> mpmath.mp.prec = 53 # but that rounds up to 15 under 53bit precision
>>> 6.0 / mpmath.mpf(0.4)
mpf('15.0')
Correct, small integers are all exact in 64bit floating point, but thatâs not why 6.0/0.4
evaluates to exactly 15.0.
Under IEEE754 maths, calculations are required to be correctly rounded. Anyone who is old enough to remember computer arithmetic before IEEE754 can probably tell you many horror stories, such as
 x != y but x  y = 0.0
 x == y but x/y != 1.0
These abominations are impossible in IEEE754 maths. But I digress.
Iâm not an expert here, but my understanding is that arithmetic in IEEE754 must use an extra three bits. So when we divide two 64bit floats, the calculation actually uses 67 bits for the calculation, and then round the result back down to 64 when done.
When we calculate 6.0/0.4
weâre actually computing
6.0/(0.4 + d)
for some tiny value d, which works out as 15.0  e
for some other tiny value e. In this specific case, it just so happens that the difference between the true value 15.0 and the computed value 15.0  e
is small enough that when the three guard digits are rounded off, the result ends up being the true value 15.0.
But that isnât guaranteed to always happen with every division:
(6*7)/(0.4*7) # Mathematically exact result = 15.0
> but returns 14.999999999999998
IEEE754 semantics guarantee that when you divide two binary numbers, the result is always correctly rounded in binary. That doesnât necessarily mean that the result looks correct in decimal, but sometimes we get lucky:
 the exact value 6.0
 when divided by the inexact value 0.4
 gives a value slightly less than 15.0 when using 3 extra bits
 which when rounded back to 64 bits ends up being the exact value 15.0
 and multitudes rejoice.
Whereas the alternate calculation ends up rounding down to something a tad under the mathematically true value.
Thanks Tim, that does it explain it, though to my naive eye, pythonâs floor division is not doing the right (or the best) thing here â the results woul be less surprising, if not more correct if:
a // b
gave the same result as math.floor( a / b)
, which it is does not:
In [137]: 6 // 0.4 == math.floor(6 / 0.4)
Out[137]: False
Also in my naive view, Iâm surprised //
isnât in fact implemented that way
As far as I could tell with a quick search IEEE 754 doesnât define âfloor divisionâ â I wonder what it would specify if it did?
NOTE: before floor division was defined in Python, I imagine folks would have done math.floor(a / b)
â so an incompatibility was introduced.
In fact, if you use integers too large to be exactly represented by floats, then integer division IS different than floor division, which strikes me as suboptimal:
In [148]: a
Out[148]: 60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
In [149]: b
Out[149]: 4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
In [150]: a / b
Out[150]: 15.0
In [151]: float(a) // float(b)
Out[151]: 14.0
Granted, for practical purposes, unlikely to come up, but still.
A better correlation would be that a // b
should be the same as divmod(a, b)[0]
, that a % b
should be the same as divmod(a, b)[1]
, and then that divmod is defined using this rule:
q, r = divmod(a, b)
assert q * b + r == a
as is stated in divmodâs docstring. As far as I know, this should be true of all numeric types. (Sadly, I canât use divmod with strings, even if I subclass str
such that x // y
<=> x.split(y)
 it still objects to the objects Iâm feeding it. Awwww.)
The downside is, itâs notably harder to then explain floor division on its own; you have to simultaneously explain modulo.
A fully compliant 754 implementation needs to support the 4 distinct rounding modes defined by that standard. âTo minus infinityâ rounding is what it calls what weâre calling âfloorâ here. Its âto plus infinityâ rounding is what we call âceilingâ. But core Python does not support any way to get at your hardwareâs rounding mode setting. If it did, and you asked for âto minus infinityâ rounding, your hardware division would also return a result less than 15.0
for 6 / 0.4
.
>>> import mpmath
>>> mpmath.fdiv(6.0, 0.4)
mpf('15.0')
>>> mpmath.fdiv(6.0, 0.4, rounding='floor')
mpf('14.999999999999998')
Essentially all 754 operations are defined to return the infinitely precise result subject to one rounding at the end. There is no argument to be made about the âcorrectâ result for 6 / 0.4
. Itâs precisely defined for each rounding mode.
A more fruitful question would be to wonder why Python supports âfloor divisionâ for floats at all. Itâs very useful for ints, but ints have many pleasant mathematical properties floats lack. In a similar vein, the closely related definition for integer %
results isnât really what floats want either.
So it goes. A great many languages are seduced by the observation that, mathematically, the integers are a subset of the reals. Thatâs where ânumeric towerâ wishful thinking comes from. But machine floats are very far from being âthe realsâ. Fixedprecision machine floats are in fact a strangely lumpy finite subset of the rationals, augmented with weird âinfinityâ and âNaNâ special cases.
Floor division was part of Python from day #1, so there is no âbeforeâ. However, for floats, to get at it you needed to spell it at first as divmod(x, y)[0]
.
Nothing about the meaning of that fragment has changed. /
has always meant âtrue divisionâ for floats. Although Iâm lying a bit: math.floor()
, until quite recently, returned a float instead of an int, mirroring what the C libm floor()
does. Changing that was not, IMO, a good idea either.
I think you are surprised because you canât get past the fact that the literal 0.4
looks like the exact decimal fraction 4 over 10, and so you think 6/0.4
is exactly correct and 6//4
has rounding error.
This is where the convenience of being able to enter and display binary floats in decimal bites us on the derriĂ¨re and gives us the wrong intuitions about floats. It makes us surprised about things we shouldnât be surprised about, and take for granted results which should be astonishing.
Instead of using the literal 0.4
, letâs suppose your divisor was the result of some long, complicated computation, where you have no expectation that it would end up exactly one fifteenth of 6:
Here is an example of such a complicated computation, which is totally not contrived
x = 7205759403792794.0 * 5.551115123125783e17
If we compute 6//x
without first inspecting the value of x, and get 14.0, weâre not surprised in the least. We had to get some whole number, and 14.0 is no more or less surprising than any other.
If we now inspect the value of x to full precision, we see something like 0.40000âŚ7263336181640625 and weâre hardly surprised that the number we computed is a âweird decimalâ, nearly all numbers are âweirdâ in the sense of having a bunch of digits with no obvious pattern.
The only strange thing about this x is that thereâs a bunch of 0s in a row in the middle of the number, but thatâs not that surprising. Lots of numbers have a bunch of zeroes in them.
So now letâs take that âweird decimalâ x, and divide it into 6. It would be some coincidence if it happened to divide exactly into 6 and give a whole number result, right?
In fact, if we look at the long tail of digits in the exact decimal representation âŚ7263336181640625 it would be astonishing if it divided exactly, we ought to get something like 14.9999999âŚ987 or 15.000000âŚ132 or something.
And yet, as if by magic, the rounding error in 6/x
disappears and leaves us with exactly 15.0.
I donât know about anyone else, but I find it far more amazing that binary floating point arithmetic ever gives the mathematically correct values I expect in decimal than the fact that sometimes it doesnât.
Well, no, Iâm not â I thought I made it clear that I was surprised because I expected âfloor divisionâ to be the same as taking the floor after dividing â i.e. floor(a / b) â and itâs not, which Tim explained quite nicely.
I think Timâs point about maybe floor division being a tricky idea for floats is a good one.
For that matter, Iâve been surprised more than once that a_float // a_float returns a float â isnât it always supposed to be an integral value?
if floor(a_float)
returns an int, then why not floor division?
(Tim argues that floor()
shouldnât return and int â which would at least be consistent)
Iâm particularly surprised, because in_int // an_int
does return an int, and the whole thing was created to clear up the surprises that we got when the result of an operation depended on the type (rather than the value) of the operators.
This has practical considerations â Iâve often used // to compute an index â having to wrap a int() around the result is a bit annoying.
By the way, I may have been wrong, I think folks would expect that:
a // b
would be the same as int(a / b)
â which it also doesnât.
Oh well.