Alexander Soare
Alexander Soare

Reputation: 3247

How does Python remember the number of decimal places one used to specify a float?

Today it was pointed out to me that 0.99 can't be represented by a float:

num = 0.99
print('{0:.20f}'.format(num))

prints 0.98999999999999999112. I'm fine with this concept.

So then how does python know to do this:

num = 0.99
print(num)

prints 0.99.

Upvotes: 2

Views: 520

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1074198

How does Python remember the number of decimal places one used to specify a float?

It doesn't. Try this:

num = 0.990
print(num)

Notice that that also outputs 0.99, not 0.990.

I can't speak specifically for the print function, but it's common in environments that have IEEE-754 double-precision binary floating point numbers to use an algorithm that outputs only as many digits as are needed to differentiate the number from its closest "representable" neighbour. But it's much more complicated than it might seem on the surface. See this paper on number rounding for details (associated code here and here).

Sam Mason provided some great links related to this:

  • From Floating Point Arithmetic: Issues and Limitations

    This bears out the "closest representable" thing above. It starts by describing the issue in base 10 that you can't accurately represent one-third (1/3). 0.3 comes close, 0.33 comes closer, 0.333 comes even closer, but really 1/3 is an infinitely repeating series of 0.3 followed by 3s forever. In the same way, binary float point (which stores the number as a base 2 fraction rather than a base 10 fraction) can't exactly represent 0.1 (for instance), like 1/3 in base 10 it's an infinitely repeating series of digits in base 2 and anything else is an approximation. It then continues:

    In the same way, no matter how many base 2 digits you’re willing to use, the decimal value 0.1 cannot be represented exactly as a base 2 fraction. In base 2, 1/10 is the infinitely repeating fraction

    0.0001100110011001100110011001100110011001100110011... Stop at any finite number of bits, and you get an approximation. On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two. In the case of 1/10, the binary fraction is 3602879701896397 / 2 ** 55 which is close to but not exactly equal to the true value of 1/10.

    Many users are not aware of the approximation because of the way values are displayed. Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine. On most machines, if Python were to print the true decimal value of the binary approximation stored for 0.1, it would have to display

    >>> 0.1

    0.1000000000000000055511151231257827021181583404541015625

    That is more digits than most people find useful, so Python keeps the number of digits manageable by displaying a rounded value instead

    >>> 1 / 10

    0.1

    Just remember, even though the printed result looks like the exact value of 1/10, the actual stored value is the nearest representable binary fraction.

  • The code for it in CPython

  • An issue discussing it on the issues list

Upvotes: 4

khelwood
khelwood

Reputation: 59114

It's not remembering. It's looking at the value it's got and deciding the best way to present it, which it thinks in this case is 0.99 because the value is as close as possible to 0.99.

If you print(0.98999999999999999112) it will show 0.99, even though that is not the number of decimal places you used to specify it.

Upvotes: 3

Related Questions