5

I'm a new to python and I found a confusing result when using Python3.5.1 on my mac, I simply ran this command in my terminal

    1 // 0.05

However, it printed 19.0 on my screen. From my point of view, it should be 20. Can someone explain what's happening here? I've already known that the '//' is similar to the math.floor() function. But I still can't get across to this.

dan04
  • 87,747
  • 23
  • 163
  • 198
Jian Guo
  • 708
  • 1
  • 9
  • 19
  • 2
    It divides then rounds down. That is about it. – Mark Skelton Mar 09 '16 at 16:49
  • 3
    @MarkyPython: There's more to it than that. On my machine, `1 / 0.05` is `20.0`, but `1 // 0.05` is `19.0`. – dan04 Mar 09 '16 at 16:51
  • 5
    Long story short: don't *expect* any particular result when dealing with floating point numbers. – deceze Mar 09 '16 at 16:52
  • 3
    Why was this reopened? IMO [Is floating point math broken?](http://stackoverflow.com/q/588004/2301450) was a good dupe target. – vaultah Mar 09 '16 at 16:57
  • Huh, I thought `//` is supposed to return `int`... – GingerPlusPlus Mar 09 '16 at 17:00
  • 3
    @vaultah I reopened it because I don't consider it to be a duplicate of http://stackoverflow.com/q/588004/2301450 . This question is specifically about how the floor division operator works. It's not a generic question about why floating point math is imprecise. – ali_m Mar 09 '16 at 17:01
  • Why is there can't you pydoc this and operators in general `pydoc(//)` – tread Mar 09 '16 at 17:04
  • 2
    @GingerPlusPlus you thought wrong. Just as with other numeric operations the type of the result is determined by the types of the operands. It can return `int`, `float`, `Decimal`, `complex` and so on. – Duncan Mar 09 '16 at 17:04
  • @surfer190: You _can_ do `pydoc '//'` or `help('//')`, although I admit the result is of limited usefulness, apart from the precedence info. – PM 2Ring Mar 09 '16 at 17:09
  • 2
    For exact **decimal** fractions you should be using `decimal.Decimal`. – Antti Haapala -- Слава Україні Mar 09 '16 at 17:10

3 Answers3

7

Because the Python floating-point literal 0.05 represents a number very slightly larger than the mathematical value 0.05.

>>> '%.60f' % 0.05
'0.050000000000000002775557561562891351059079170227050781250000'

// is floor division, meaning that the result is the largest integer n such that n times the divisor is less than or equal to the dividend. Since 20 times 0.05000000000000000277555756156289135105907917022705078125 is larger than 1, this means the correct result is 19.

As for why the Python literal 0.05 doesn't represent the number 0.05, as well as many other things about floating point, see What Every Computer Scientist Should Know About Floating-Point Arithmetic

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Also, it does not return an *integer* in this case, but float. – Antti Haapala -- Слава Україні Mar 09 '16 at 17:08
  • 1
    @AnttiHaapala: I did wonder whether I'd get away with that terminology. It returns a floating-point value that represents an integer. What it doesn't return, is the Python type `int` (or `long`), but I'm using the word "integer" here to mean the mathematical concept of an integer, not the Python type called `int`. – Steve Jessop Mar 09 '16 at 17:10
  • @SteveJessop Thanks for your help, I really should have been careful with floating-point value in programming. – Jian Guo Mar 10 '16 at 01:13
3

0.05 is not exactly representable in floating point. "%0.20f" % 0.05 shows that 0.05 is stored as a value very slightly greater than the exact value:

>>> print "%0.20f" % 0.05
0.05000000000000000278

On the other hand 1/0.05 does appear to be exactly 20:

>>> print "%0.20f" % (1/0.05)
20.00000000000000000000

However all floating point values are rounded to double when stored but calculations are done to a higher precision. In this case it seems the floor operation performed by 1//0.05 is done at full internal precision hence it is rounded down.

Duncan
  • 92,073
  • 11
  • 122
  • 156
1

As the previous answerers have correctly pointed out, the fraction 0.05 = 1/20 cannot be exactly represented with a finite number of base-two digits. It works out to the repeating fraction 0.0000 1100 1100 1100... (much like 1/3 = 0.333... in familiar base-ten).

But this is not quite a complete answer to your question, because there's another bit of weirdness going on here:

>>> 1 / 0.05
20.0
>>> 1 // 0.05
19.0

Using the “true division” operator / happens to give the expected answer 20.0. You got lucky here: The rounding error in the division exactly cancels out the error in representing the value 0.05 itself.

But how come 1 // 0.05 returns 19? Isn't a // b supposed to be the same as math.floor(a /b)? Why the inconsistency between / and //?

Note that the divmod function is consistent with the // operator:

>>> divmod(1, 0.05)
(19.0, 0.04999999999999995)

This behavior can be explained by performing computing the floating-point division with exact rational arithmetic. When you write the literal 0.05 in Python (on an IEEE 754-compliant platform), the actual value represented is 3602879701896397 / 72057594037927936 = 0.05000000000000000277555756156289135105907917022705078125. This value happens to be slightly more than the intended 0.05, which means that its reciprocal will be slightly less.

To be precise, 72057594037927936 / 3602879701896397 = 19.999999999999998889776975374843521206126552300723564152465244707437044687...

So, // and divmod see an integer quotient of 19. The remainder works out to 0.04999999999999994726440633030506432987749576568603515625, which is rounded for display as 0.04999999999999995. So, the divmod answer above is in fact good to 53-bit accuracy, given the original incorrect value of 0.05.

But what about /? Well, the true quotient 72057594037927936 / 3602879701896397 isn't representable as a float, so it must be rounded, either down to 20-2**-48 (an error of about 2.44e-15) or up to 20.0 (an error of about 1.11e-15). And Python correctly picks the more accurate choice, 20.0.

So, it seems that Python's floating-point division is internally done with high enough precision to know that 1 / 0.05 (that's the float literal 0.05, not the exact decimal fraction 0.05), is actually less than 20, but the float type in itself is incapable of representing the difference.

At this point you may be thinking “So what? I don't care that Python is giving a correct reciprocal to an incorrect value. I want to know how to get the correct value in the first place.” And the answer to that is either:

dan04
  • 87,747
  • 23
  • 163
  • 198