Strange behaviour with floats and string conversion

The crucial requirement on repr is that it should round-trip; that is, eval(repr(f)) == f should give True in all cases.

In Python 2.x (before 2.7) repr works by doing a printf with format %.17g and discarding trailing zeroes. This is guaranteed correct (for 64-bit floats) by IEEE-754. Since 2.7 and 3.1, Python uses a more intelligent algorithm that can find shorter representations in some cases where %.17g gives unnecessary non-zero terminal digits or terminal nines. See What’s new in 3.1? and issue 1580.

Even under Python 2.7, repr(0.1 * 0.1) gives "0.010000000000000002". This is because 0.1 * 0.1 == 0.01 is False under IEEE-754 parsing and arithmetic; that is, the nearest 64-bit floating-point value to 0.1, when multiplied by itself, yields a 64-bit floating-point value that is not the nearest 64-bit floating-point value to 0.01:

>>> 0.1.hex()
'0x1.999999999999ap-4'
>>> (0.1 * 0.1).hex()
'0x1.47ae147ae147cp-7'
>>> 0.01.hex()
'0x1.47ae147ae147bp-7'
                 ^ 1 ulp difference

The difference between repr and str (pre-2.7/3.1) is that str formats with 12 decimal places as opposed to 17, which is non-round-trippable but produces more readable results in many cases.

Leave a Comment