5

I'm porting a MATLAB code to Python 3.5.1 and I found a float round-off issue.

In MATLAB, the following number is rounded up to the 6th decimal place:

fprintf(1,'%f', -67.6640625);
-67.664063

In Python, on the other hand, the following number is rounded off to the 6th decimal place:

print('%f' % -67.6640625)
-67.664062

Interestingly enough, if the number is '-67.6000625', then it is rounded up even in Python:

print('%f' % -67.6000625)
-67.600063

... Why does this happen? What are the criteria to round-off/up in Python? (I believe this has something to do with handling hexadecimal values.)

More importantly, how can I prevent this difference? I'm supposed to create a python code which can reproduce exactly the same output as MATLAB produces.

IanHacker
  • 541
  • 9
  • 27
  • 2
    I was curious so I looked up the behaviour of this in numpy as well. What I got was odd. `np.around(a,6)` (with a being your first value) yields -67.664062000000001. The second value you tried yields: -67.600061999999994. [The official python documentation](https://docs.python.org/3/tutorial/floatingpoint.html) explains this behaviour. – stonecharioteer Aug 27 '16 at 15:53
  • 2
    I tried both C and Octave and they all print `-67.664062`, the same as Python. – kennytm Aug 27 '16 at 15:56
  • 3
    Considering how tricky float comparision have always been (like 3.0*2.2 != 2.0*3.3), I would start by questionning the feasability of the project. Best practice for comparing two floats is always to compare the difference to some tolerance. – jadsq Aug 27 '16 at 15:59
  • 1
    Also see https://docs.python.org/3/library/functions.html#round and see http://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior for some suggestions. – PM 2Ring Aug 27 '16 at 16:01
  • 1
    I'd suggest not using an implicit form of rounding off. Instead, ensure that your code is doing what you **expect** it to. **[This](http://stackoverflow.com/a/22155830/3833764) answer** is an excellent summary on what you want to do. – stonecharioteer Aug 27 '16 at 16:07

1 Answers1

4

The reason for the python behavior has to do with how floating point numbers are stored in a computer and the standardized rounding rules defined by IEEE, which defined the standard number formats and mathematical operations used on pretty much all modern computers.

The need to store numbers efficiently in binary on a computer has lead computers to use floating-point numbers. These numbers are easy for processors to work with, but have the disadvantage that many decimal numbers cannot be exactly represented. This results in numbers sometimes being a little off from what we think they should be.

The situation becomes a bit clearer if we expand the values in Python, rather than truncating them:

>>> print('%.20f' % -67.6640625)
-67.66406250000000000000
>>>  print('%.20f' % -67.6000625)
-67.60006250000000704858

So as you can see, -67.6640625 is a number that can be exactly represented, but -67.6000625 isn't, it is actually a little bigger. The default rounding mode defined by the IEEE stanard for floating-point numbers says that anything above 5 should be rounded up, anything below should be rounded down. So for the case of -67.6000625, it is actualy 5 plus a small amount, so it is rounded up. However, in the case of -67.6640625, it is exactly equal to five, so a tiebreak rule comes into play. The default tiebreaker rule is round to the nearest even number. Since 2 is the nearest event number, it rounds down to two.

So Python is following the approach recommended by the floating-point standard. The question, then, is why your version of MATLAB doesn't do this. I tried it on my computer with 64bit MATLAB R2016a, and I got the same result as in Python:

>> fprintf(1,'%f', -67.6640625)
-67.664062>>

So it seems like MATLAB was, at some point, using a different rounding approach (perhaps a non-standard approach, perhaps one of the alternatives specified in the standard), and has since switched to follow the same rules as everyone else.

TheBlackCat
  • 9,791
  • 3
  • 24
  • 31
  • Wow, well-explained. I'm completely convinced. Yes, I'm using MATLAB R2011b, so a bit older. I thought MATLAB was always right, but I was wrong. One good thing is that I don't have to fix my Python code for this issue. ;-) Thank you very much! – IanHacker Aug 28 '16 at 06:09