Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I lose precision while multiplying and dividing whole ints?

I had thought Python3 is supposed to be able to handle numbers of arbitrary length, but I'm running into a problem where they don't seem to act consistently.

After multiplying then dividing, my int seems to have changed it's internal representation, and no longer evaluates as a match for it's former self.

I'm using whole numbers, without any decimals or fractions, but it's acting almost as if it's losing precision to rounding..?

I'd appreciate any insight on why this is happening, and if there's something I should be doing differently. I have workarounds for my code, but since the result is counter-intuitive, I'd love to know what's behind the behavior ;)

Python 3.3.2 (default, Jul 30 2013, 00:52:04) 
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 313585730727764141482831584863
>>> a
313585730727764141482831584863
>>> b = a*2
>>> c = b /2
>>> a
313585730727764141482831584863
>>> c
3.1358573072776415e+29
>>> a == c
False

This seems to work if I use floor division, however-

>>> c = b//2
>>> c
313585730727764141482831584863
>>> a == c
True

Python 2.7 also seems to avoid this scenerio, keeping them in longs

>>> a = 313585730727764141482831584863
>>> a
313585730727764141482831584863L
>>> b = a*2
>>> c = b /2
>>> a
313585730727764141482831584863L
>>> c
313585730727764141482831584863L
>>> a == c
True

I'd appreciate any insight! Thank you!

like image 468
Colin Davis Avatar asked Sep 14 '13 13:09

Colin Davis


1 Answers

You are dividing using the true division operator /, which will always result in floating point values. Use floor division instead, //, to get integer results:

>>> a = 313585730727764141482831584863
>>> b = a*2
>>> c = b // 2
>>> a == c
True

Your computer hardware cannot handle float values with the required precision.

The alternative is to use decimal.Decimal() values, but this will result in slower arithmetic operations.

In Python 2, the / operator is the floor division operator, but when applied to integers only. To get the same behaviour in Python 2, add:

from __future__ import division

The behaviour was changed because the difference between using only integer operators and using at least one float argument was confusing.

In other words, the standard Python 2 / operator is a different beast from the Python 3 / division operator. When applied to two integer operands, it acts just like the // floor division operator in Python 3. But if either one of the two operands is a float instead, then it acts like the / float division operator instead. The above __future__ import swaps out the Python 2 / operator for the true division operator found in Python 3.

You can see this when disassembling Python bytecode:

>>> import dis
>>> def olddivision(x, y): return x / y
... 
>>> dis.dis(olddivision)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_FAST                1 (y)
              6 BINARY_DIVIDE       
              7 RETURN_VALUE        
>>> from __future__ import division
>>> def newdivision(x, y): return x / y
... 
>>> dis.dis(newdivision)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_FAST                1 (y)
              6 BINARY_TRUE_DIVIDE  
              7 RETURN_VALUE        

the __future__ import caused the Python compiler to use a different bytecode for the division operator, swapping BINARY_DIVIDE for BINARY_TRUE_DIVIDE.

like image 171
Martijn Pieters Avatar answered Sep 20 '22 12:09

Martijn Pieters