Search code examples
pythonfloating-pointprecisionfloating-accuracy

How to show true numeric value in Python?


Case 1:

for num in [.1, .2, .3, .4, .5, .6, .7, .8, .9,]:
    print(format(num, ".50f"))
0.10000000000000000555111512312578270211815834045410
0.20000000000000001110223024625156540423631668090820
0.29999999999999998889776975374843459576368331909180
0.40000000000000002220446049250313080847263336181641
0.50000000000000000000000000000000000000000000000000
0.59999999999999997779553950749686919152736663818359
0.69999999999999995559107901499373838305473327636719
0.80000000000000004440892098500626161694526672363281
0.90000000000000002220446049250313080847263336181641

Imprecision, as expected (except .5).


Case 2:

for num in [1., 2., 3., 4., 5., 6., 7., 8., 9.]:
    print(format(num, ".50f"))
1.00000000000000000000000000000000000000000000000000
2.00000000000000000000000000000000000000000000000000
3.00000000000000000000000000000000000000000000000000
4.00000000000000000000000000000000000000000000000000
5.00000000000000000000000000000000000000000000000000
6.00000000000000000000000000000000000000000000000000
7.00000000000000000000000000000000000000000000000000
8.00000000000000000000000000000000000000000000000000
9.00000000000000000000000000000000000000000000000000

Perfect precision - ???


As is known, there's no such thing as a perfect float integer in computing: all floats are represented in terms of a binary base, with increasing precision depending on bitsize (float32, float64, etc). So what's the deal with Case 2 above? The zeros persist even for ".1000f", basically implying infinite precision. Further, 0.5 is also somehow represented perfectly.

If format cannot force Python to print the "true" value of a float, then what can?


Attempted alternatives:

  1. format(round(num, 50), ".50f")
  2. format(numpy.float128(num), ".50f")
  3. format(round(numpy.float128(num), 50), ".50f")
  4. format("%.50f" % num)
  5. "{:.50f}".format(num))
  6. f"{num:.50f}"

ACCEPTED ANSWER: clarifies false premise assumed in the question; the answer to the actual question is within the question itself - use format to show true numeric value.


Solution

  • In commonly used formats, such as IEEE 754 64-bit binary floating point, all finite float numbers are binary fractions, numbers of the form A*2B where A and B are both signed integers.

    Of course, a finite format can only represent a finite subset of the binary fractions. Both the number of significant bits in A and the range of B are limited by the format. For normal (not subnormal) IEEE754 64-bit binary, A can have no more than 53 significant bits, and, with non-zero A normalized to the form 1.x, B has to be in the range [−1022, 1023].

    0.5 can be represented exactly because it is 1*2-1. Similarly, numbers such as 5.0/8.0 (5*2-3) are exact.

    In 64-bit binary floating point all integers that fit in 32 bit binary can be represented exactly, explaining the second table in the question. 9 is 9*20.

    It is worth noting for the output side that every binary fraction has a terminating decimal expansion. This is a consequence of 2 being a factor of 10. Print enough digits and you will get the exact value of the floating point number.