Python’s standard floating-point numbers (float type) represent values internally using binary. Therefore, even simple decimals like 0.1 cannot be represented accurately, causing minute “errors” during calculation.
In scenarios requiring strict calculations where even slight errors are impermissible, such as financial or scientific calculations, the decimal module from the standard library must be used.
This article explains how to perform accurate calculations using the Decimal type and the “anti-patterns” to avoid when generating instances.
Calculation Errors in Floating-Point Numbers (float)
First, let’s confirm what problems occur when calculating with the float type. For example, adding 0.1 three times should result in 0.3, but with float, it behaves as follows:
# Calculation with float type
val_float = 0.1 + 0.1 + 0.1
print(f"Result: {val_float}")
print(f"Is it equal to 0.3: {val_float == 0.3}")
Execution Result:
Result: 0.30000000000000004
Is it equal to 0.3: False
This error stems from the computer approximating decimals using binary numbers.
Accurate Calculations with the Decimal Type
Using the Decimal class from the decimal module allows numbers to be treated accurately as decimal numbers, avoiding such errors.
Correct Usage: Initialize with Strings
When creating a Decimal object, the golden rule is to pass the number as a “string” to the argument.
from decimal import Decimal
# Pass values as strings
d1 = Decimal("0.1")
d2 = Decimal("0.1")
d3 = Decimal("0.1")
# Calculation between Decimals
total_decimal = d1 + d2 + d3
print(f"Result: {total_decimal}")
print(f"Is it equal to Decimal('0.3'): {total_decimal == Decimal('0.3')}")
Execution Result:
Result: 0.3
Is it equal to Decimal('0.3'): True
The result became 0.3 as expected, and the equality check returned True.
Specific Usage Example: Currency Calculation
For example, consider the calculation “subtract 9.95 dollars from 10.00 dollars.” This is another calculation prone to errors with float.
from decimal import Decimal
# Case with float
price_float = 10.00
cost_float = 9.95
print(f"Result with float: {price_float - cost_float}")
# Case with Decimal (Initialize with strings)
price_dec = Decimal("10.00")
cost_dec = Decimal("9.95")
result_dec = price_dec - cost_dec
print(f"Result with Decimal: {result_dec}")
Execution Result:
Result with float: 0.049999999999998934
Result with Decimal: 0.05
By using Decimal, the correct result of 0.05 was obtained.
Caution: Do Not Initialize with float Values
The most common mistake when using Decimal is passing a float type number directly to the constructor.
A float number (e.g., 0.1) becomes an “approximate value containing errors” the moment it is written in the program. Passing this to Decimal will preserve that error with high precision.
# Bad example: Initialization with float type
# Instead of "0.1", the approximation of 0.1 is stored directly
bad_decimal = Decimal(0.1)
print(f"Created with string: {Decimal('0.1')}")
print(f"Created with number: {bad_decimal}")
Execution Result:
Created with string: 0.1
Created with number: 0.1000000000000000055511151231257827021181583404541015625
As shown, passing a number directly results in an unintended value. For literals, always enclose them in quotes. If converting from a variable, use the str() function to convert it to a string before passing it to Decimal.
# When converting a float value stored in a variable
input_val = 0.1
safe_decimal = Decimal(str(input_val)) # Convert to string first
Summary
- Use the
decimalmodule for calculations where errors are not allowed, such as money or precise measurements. - When creating
Decimalobjects, always pass strings as arguments, likeDecimal("0.1"). - Avoid passing numbers directly, like
Decimal(0.1), as this inherits the floating-point error.
