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.97000000000001

Decimal 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.97

Creating 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.23

Always 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.1429

Rounding

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.99

Rounding 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.5

Math 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())  # True

Financial 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.46

Performance 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: