Python Decimal Module - Undesired Float-Like Output?
Asked Answered
M

3

1

This I imagine is extremely simple - but why in the following are the two values for y not == 0? I thought the whole point of the decimal module was to get rid of the float dust ...The following is an extremely simplified version of a mathematical routine that passes numbers around as variables.

from decimal import * 
getcontext().prec = 2

q = Decimal(0.01)

x = Decimal(0.10) * Decimal(0.10)

y = Decimal(x) - Decimal(q)

print(x,y, Decimal(y))

'''
x== 0.010 
y== -2.1E-19 
Decimal(y) == -2.1E-19
'''
Marine answered 21/7, 2015 at 3:34 Comment(0)
R
3

The float literal 0.10 is not precisely the mathematical number 0.10, using it to initialize Decimal doesn't avoid the float precision problem.

Instead, using strings to initialize Decimal can give you expected result:

x = Decimal('0.10') * Decimal('0.10')
y = Decimal(x) - Decimal('0.010')
Rizas answered 21/7, 2015 at 3:39 Comment(2)
Hey thanks, it's good to know Decimal like strings, I edited the question a bit in light of your response thoughMarine
No need to cast x to Decimal, it is already. The usual arithmetic operators on Decimals produce a Decimal result.Couch
S
3

Try specifying the numbers as strings

>>> Decimal('0.10') * Decimal('0.10') - Decimal('0.0100')
>>> Decimal('0.000')
Selfsupporting answered 21/7, 2015 at 3:40 Comment(1)
Hey thanks, it's good to know Decimal like strings, I edited the question a bit in light of your response thoughMarine
R
2

This is a more detailed explanation of the point made in existing answers.

You really do need to get rid of the numeric literals such as 0.1 if you want exact decimal arithmetic. The numeric literals will typically be represented by IEEE 754 64-bit binary floating point numbers.

The closest such number to 0.1 is 0.1000000000000000055511151231257827021181583404541015625. Its square is 0.01000000000000000111022302462515657123851077828659396139564708135883709660962637144621112383902072906494140625, which is not the same as the closest to 0.01, 0.01000000000000000020816681711721685132943093776702880859375.

You can get a clearer view of what is going on by removing the prec =2 context, allowing more precise output:

from decimal import * 
q = Decimal(0.01)
x = Decimal(0.10) * Decimal(0.10)
y = Decimal(x) - Decimal(q)
print(q)
print(x)
print(y)

Output:

0.01000000000000000020816681711721685132943093776702880859375
0.01000000000000000111022302463
9.020562075127831486705690622E-19

If you had used string literals, as suggested by the other responses, the conversion to Decimal would have been done directly, without going through binary floating point. Both 0.1 and 0.01 are exactly representable in Decimal, so there would be no rounding error.

Readily answered 21/7, 2015 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.