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

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

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

Local 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 context

Rounding

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

Currency 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.57

Arithmetic 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.25

Comparison

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

Special 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.01

Database 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 slower

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

React to this post: