123.3 + 0.1 is 123.3999999999 ?

Dan Bishop danb_83 at yahoo.com
Fri May 16 02:16:50 EDT 2003


A Puzzled User <kendear_nospam at nospam.com> wrote in message news:<3EC3BCCD.3090605 at nospam.com>...
> In Python 2.2.2
>
>  >>> float("123.4")+0.1
> 123.5
> 
>  >>> float("123.3")+0.1
> 123.39999999999999
> 
>  >>> float("123.1") + 1
> 124.09999999999999
> 
> how come there are these inaccuracies?

To make it easier to see the "problem", I will rewrite all the numbers
in the same base the computer uses, i.e., base 2.

decimal 123.4 + 0.1
= binary 1111011.0110 0110 0110 0110... + 0.0 0011 0011 0011...

decimal 123.3 + 0.1
= binary 1111011.0 1001 1001 1001... + 0.0 0011 0011 0011...

decimal 123.1 + 1
= binary 1111011.0 0011 0011 0011... + 1

Note that 1/10, 3/10, and 4/10 have repeating fraction digits in
binary.  (I would have said "repeating decimals", but that gets
confusing when you aren't using base ten.)  This is analogous to 1/3 =
0.333... in base 10 (but not, for example, in base 12, where 1/3 =
0.4).

The computer doesn't store an infinite number of bits, so the
repeating bits have to get rounded off at some point.  In IEEE 754
double-precision (the usual underlying type of Python "float"), the
exact values the numbers get rounded to (expressed in C99 hex
literals) are

  0.1 = 0x1.999999999999Ap-4
123.1 = 0x1.EC66666666666p+6
123.3 = 0x1.ED33333333333p+6
123.4 = 0x1.ED9999999999Ap+6

and the results of the additions are

123.4 + 0.1
= 0x1.ED9999999999Ap+6 + 0x1.999999999999Ap-4
# denormalize the smaller number to get the same exponent
= 0x1.ED9999999999Ap+6 + 0x0.0066666666666668p+6
= 0x1.EE00000000000668p+6
# round to 53 significant bits
= 0x1.EE00000000000p+6
= 123.5 (exactly)

By coincidence, the result is exactly equal to a "nice" decimal.  But
this isn't usually the case.  For example,

123.3 + 0.1
= 0x1.ED33333333333p+6 + 0x1.999999999999Ap-4
= 0x1.ED99999999999668p+6
= 0x1.ED99999999999p+6 (rounded)
= 123.3999999999999914734871708787977695465087890625

123.1 + 1
= 0x1.EC66666666666p+6 + 1
= 0x1.F066666666666p+6
= 124.099999999999994315658113919198513031005859375

Most people don't like seeing long strings of "noise" digits, so
repr(x) doesn't return the exact value stored, but a reasonably short
decimal string approximating the value.  But it must have the property
that eval(repr(x)) == x.

So why not just print "123.4" and "124.1"?  For the last example,
"124.1" would be OK, because eval("124.1") is indeed equal to
0x1.F066666666666p+6.  Unfortunately, eval("123.4") is
0x1.ED9999999999Ap+6, which isn't the correct value of
0x1.ED99999999999p+6.

So what should repr(123.3 + 0.1) be?  Well, let's look at the next
nearest representable values that 123.3 + 0.1 needs to be
distinguished from.

0x1.ED99999999998p+6 = 123.3999999999999772626324556767940521240234375
0x1.ED99999999999p+6 =
123.3999999999999914734871708787977695465087890625
0x1.ED9999999999Ap+6 =
123.400000000000005684341886080801486968994140625

If we round to 17 significant digits, i.e.,

repr(0x1.ED99999999998p+6 ) == "123.39999999999998"
repr(0x1.ED99999999999p+6 ) == "123.39999999999999"
repr(0x1.ED9999999999Ap+6 ) == "123.40000000000001"

Then all 3 values have different reprs.  It can be shown that 17
significant digits is the minimum necessary to ensure that repr(x) !=
repr(y) whenever x != y, so repr(x) for a float is defined as "%.17g"
% x (minus any trailing 0's at the end).

Therefore,

repr(123.4 + 0.1) == "123.5"
repr(123.3 + 0.1) == "123.39999999999999"
repr(123.1 + 1) == "124.09999999999999"

These are the exact results you got.  Your Python interpreter is
working correctly.

> --------------------------------
> P.S.
> I just tried in Perl
> 
>    print eval("123.3") + 0.1;
> 
> and it gives
> 123.4

Try

print (eval("123.3") + 0.1) == 123.4

You might be surprised.




More information about the Python-list mailing list