The decimal module provides exact decimal arithmetic. When floating-point errors aren't acceptable—financial calculations, scientific measurements, user-entered values—decimal is the answer.
The Problem with Floats
Floats have precision issues:
# Floating point surprise
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
# Financial disaster waiting to happen
total = sum([0.1] * 10)
print(total) # 0.9999999999999999Decimal to the Rescue
from decimal import Decimal
# Exact arithmetic
a = Decimal('0.1')
b = Decimal('0.2')
print(a + b) # 0.3
print(a + b == Decimal('0.3')) # True
# Financial calculation
total = sum([Decimal('0.1')] * 10)
print(total) # 1.0Creating Decimals
Always use strings for exact values:
from decimal import Decimal
# Good - from string
exact = Decimal('3.14159')
# Bad - from float (inherits imprecision)
imprecise = Decimal(3.14159)
print(imprecise) # 3.141590000000000124344978758017532527446746826171875
# From integer is fine
whole = Decimal(42)Precision and Context
Control precision with context:
from decimal import Decimal, getcontext
# Set global precision
getcontext().prec = 6
result = Decimal('1') / Decimal('3')
print(result) # 0.333333
# More precision
getcontext().prec = 50
result = Decimal('1') / Decimal('3')
print(result) # 0.33333333333333333333333333333333333333333333333333Local Context
Use local context for temporary precision:
from decimal import Decimal, localcontext
value = Decimal('1') / Decimal('7')
with localcontext() as ctx:
ctx.prec = 50
high_precision = Decimal('1') / Decimal('7')
print(high_precision)
# Back to default precision outside the contextRounding
Control how rounding works:
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN, ROUND_CEILING
price = Decimal('19.995')
# Round half up (common for currency)
print(price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) # 20.00
# Round down (truncate)
print(price.quantize(Decimal('0.01'), rounding=ROUND_DOWN)) # 19.99
# Round up (ceiling)
print(price.quantize(Decimal('0.01'), rounding=ROUND_CEILING)) # 20.00Currency Formatting
Format money properly:
from decimal import Decimal, ROUND_HALF_UP
def format_currency(amount):
"""Format as currency with 2 decimal places."""
rounded = amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
return f"${rounded:,}"
price = Decimal('1234.567')
print(format_currency(price)) # $1,234.57Arithmetic Operations
All standard operations work:
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
print(a % b) # 1.5
print(a ** 2) # 110.25Comparison
Comparisons work as expected:
from decimal import Decimal
a = Decimal('0.1') + Decimal('0.2')
b = Decimal('0.3')
print(a == b) # True (unlike floats!)
print(a > b) # False
print(a >= b) # TrueSpecial Values
Handle infinity and NaN:
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 anything)Converting Back
Convert to other types:
from decimal import Decimal
d = Decimal('123.456')
# To float (may lose precision)
f = float(d)
# To int (truncates)
i = int(d)
# To string
s = str(d)Financial Example
Calculate compound interest:
from decimal import Decimal, ROUND_HALF_UP
def compound_interest(principal, rate, years, compounds_per_year):
"""Calculate compound interest with exact arithmetic."""
principal = Decimal(str(principal))
rate = Decimal(str(rate))
n = Decimal(str(compounds_per_year))
t = Decimal(str(years))
amount = principal * (1 + rate/n) ** (n * t)
return amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
result = compound_interest(1000, 0.05, 10, 12)
print(f"Final amount: ${result}") # Final amount: $1647.01Database Integration
Many ORMs map DECIMAL/NUMERIC columns to Python Decimal:
from decimal import Decimal
# SQLAlchemy example
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
price = Column(Numeric(10, 2)) # Maps to Decimal
# Django example
class Product(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)Performance Note
Decimal is slower than float:
from decimal import Decimal
import timeit
# Float operations
float_time = timeit.timeit('0.1 + 0.2', number=1000000)
# Decimal operations
decimal_time = timeit.timeit(
'a + b',
setup='from decimal import Decimal; a = Decimal("0.1"); b = Decimal("0.2")',
number=1000000
)
# Decimal is roughly 10-100x slowerFor performance-critical code with millions of operations, consider if you truly need exact precision.
When to Use Decimal
Use decimal when:
- Money and financial calculations
- User-entered decimal values
- Scientific calculations requiring exact precision
- Anywhere floating-point error is unacceptable
Use float when:
- Performance matters more than precision
- Approximate values are acceptable
- Scientific computing with numpy (which has its own precision controls)
For anything involving money, use decimal. The performance cost is worth the correctness.