Python's decimal module provides exact decimal arithmetic, avoiding the floating-point precision issues that plague financial calculations.
The Problem with Floats
# Floating point is approximate
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!
# Money calculations go wrong
price = 19.99
quantity = 3
print(price * quantity) # 59.97000000000001Decimal to the Rescue
from decimal import Decimal
# Exact arithmetic
print(Decimal('0.1') + Decimal('0.2')) # 0.3
print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3')) # True
# Money calculations
price = Decimal('19.99')
quantity = 3
print(price * quantity) # 59.97Creating Decimals
from decimal import Decimal
# From strings (preferred)
d1 = Decimal('10.5')
# From integers
d2 = Decimal(42)
# From floats (inherits imprecision!)
d3 = Decimal(0.1) # 0.1000000000000000055511151231...
# From tuples (sign, digits, exponent)
d4 = Decimal((0, (1, 2, 3), -2)) # 1.23Always use strings for exact values:
# Wrong - inherits float imprecision
bad = Decimal(0.1)
# Right - exact value
good = Decimal('0.1')Precision and Context
from decimal import Decimal, getcontext, localcontext
# Global precision (default is 28 digits)
getcontext().prec = 50
result = Decimal(1) / Decimal(7)
print(result) # 0.14285714285714285714285714285714285714285714285714
# Local context for temporary changes
with localcontext() as ctx:
ctx.prec = 4
print(Decimal(1) / Decimal(7)) # 0.1429Rounding
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN
price = Decimal('19.995')
# Round to 2 decimal places
print(price.quantize(Decimal('0.01'))) # 20.00 (default: ROUND_HALF_EVEN)
# Round half up (common in finance)
print(price.quantize(Decimal('0.01'), ROUND_HALF_UP)) # 20.00
# Truncate
print(price.quantize(Decimal('0.01'), ROUND_DOWN)) # 19.99Rounding Modes
from decimal import Decimal, ROUND_UP, ROUND_DOWN, ROUND_CEILING
from decimal import ROUND_FLOOR, ROUND_HALF_UP, ROUND_HALF_DOWN
from decimal import ROUND_HALF_EVEN, ROUND_05UP
d = Decimal('2.5')
q = Decimal('1')
print(d.quantize(q, ROUND_UP)) # 3 (away from zero)
print(d.quantize(q, ROUND_DOWN)) # 2 (toward zero)
print(d.quantize(q, ROUND_CEILING)) # 3 (toward +infinity)
print(d.quantize(q, ROUND_FLOOR)) # 2 (toward -infinity)
print(d.quantize(q, ROUND_HALF_UP)) # 3 (5 rounds up)
print(d.quantize(q, ROUND_HALF_DOWN)) # 2 (5 rounds down)
print(d.quantize(q, ROUND_HALF_EVEN)) # 2 (banker's rounding)Arithmetic Operations
from decimal import Decimal
a = Decimal('10.5')
b = Decimal('3')
print(a + b) # 13.5
print(a - b) # 7.5
print(a * b) # 31.5
print(a / b) # 3.5
print(a // b) # 3 (floor division)
print(a % b) # 1.5 (modulo)
print(a ** 2) # 110.25
print(-a) # -10.5
print(abs(-a)) # 10.5Math Functions
from decimal import Decimal
d = Decimal('16')
# Square root
print(d.sqrt()) # 4
# Exponent
print(d.exp()) # e^16
# Natural log
print(d.ln()) # ln(16)
# Log base 10
print(d.log10()) # 1.204...Comparison
from decimal import Decimal
a = Decimal('1.0')
b = Decimal('1.00')
c = Decimal('1')
# Value comparison
print(a == b == c) # True
# Check if same representation
print(a.compare(b)) # 0 (equal value)
print(a.compare_total(b)) # -1 (different representations)Special Values
from decimal import Decimal
inf = Decimal('Infinity')
neg_inf = Decimal('-Infinity')
nan = Decimal('NaN')
print(inf > 1000000) # True
print(nan == nan) # False (NaN never equals itself)
print(nan.is_nan()) # True
print(inf.is_infinite()) # TrueFinancial Example
from decimal import Decimal, ROUND_HALF_UP
def calculate_tax(subtotal, tax_rate):
"""Calculate tax rounded to cents."""
subtotal = Decimal(str(subtotal))
rate = Decimal(str(tax_rate))
tax = subtotal * rate
return tax.quantize(Decimal('0.01'), ROUND_HALF_UP)
def calculate_total(items):
"""Sum prices with exact arithmetic."""
total = Decimal('0')
for price, quantity in items:
total += Decimal(str(price)) * quantity
return total
# Shopping cart
items = [
(19.99, 2),
(5.49, 3),
(12.00, 1),
]
subtotal = calculate_total(items)
tax = calculate_tax(subtotal, 0.0825)
total = subtotal + tax
print(f"Subtotal: ${subtotal}")
print(f"Tax (8.25%): ${tax}")
print(f"Total: ${total}")Converting Back
from decimal import Decimal
d = Decimal('123.456')
# To float (loses precision)
f = float(d)
# To int (truncates)
i = int(d)
# To string
s = str(d)
# Formatted
print(f"${d:.2f}") # $123.46Performance Note
Decimal is slower than float. Use it where precision matters (money, measurements), not for performance-critical math (scientific computing, graphics).
Summary
The decimal module is essential for:
- Financial calculations
- Any math requiring exact decimal representation
- Avoiding floating-point surprises
Use Decimal('string') (not floats), set precision via context, and choose the right rounding mode for your use case.
React to this post: