Skip to content

decimal Module Complexity

The decimal module provides support for decimal floating point arithmetic with arbitrary precision, avoiding binary floating point rounding errors.

Complexity Reference

Operation Time Space Notes
Decimal(value) O(n) O(n) n = digits in value
Decimal.from_float(f) O(1) O(1) Convert from float
Addition/Subtraction O(n) O(n) n = max digits
Multiplication O(n²) O(n) Grade-school; Python uses Karatsuba for large n
Division O(n²) O(n) Long division algorithm
quantize() O(n) O(n) Round to precision
Comparison O(n) O(1) n = digits to compare

Creating Decimal Objects

Basic Creation

from decimal import Decimal

# Create from string - O(n) where n = string length
d1 = Decimal('3.14')    # O(4)
d2 = Decimal('0.1')     # O(3)
d3 = Decimal('123456')  # O(6)

# Create from integer - O(n)
d4 = Decimal(42)        # O(2)

# Create from float (caution) - O(1)
d5 = Decimal(0.1)       # O(1) but inaccurate!
# Result: Decimal('0.1000000000000000055511151231257827021181583404541015625')

Why Not Floats?

from decimal import Decimal

# Float rounding error
print(0.1 + 0.2)  # 0.30000000000000004

# Decimal avoids this - O(n) for each operation
d1 = Decimal('0.1')
d2 = Decimal('0.2')
result = d1 + d2  # O(1 + 1) = O(1) - perfect precision
# Result: Decimal('0.3')

Arithmetic Operations

Basic Operations

from decimal import Decimal

# Addition - O(n) where n = max digits
a = Decimal('10.5')
b = Decimal('20.3')
result = a + b  # Decimal('30.8') - O(4)

# Subtraction - O(n)
result = b - a  # Decimal('9.8') - O(4)

# Multiplication - O(n²) for grade-school multiply
a = Decimal('123')  # 3 digits
b = Decimal('456')  # 3 digits
result = a * b  # O(9) - quadratic
# Result: Decimal('56088')

# Division - O(n²)
a = Decimal('100')
b = Decimal('3')
result = a / b  # O(n²) where n = precision
# Result: Decimal('33.33333333333333333333333333')

Setting Precision

from decimal import Decimal, getcontext

# Get current context
ctx = getcontext()
print(ctx.prec)  # Default: 28 digits

# Set precision - affects all operations
ctx.prec = 10

# Now operations use 10 digits - O(n)
result = Decimal('1') / Decimal('3')
print(result)  # Decimal('0.3333333333')

# Restore
ctx.prec = 28

Common Operations

Rounding

from decimal import Decimal, ROUND_HALF_UP

# Create decimal
d = Decimal('2.675')

# Quantize to 2 decimal places - O(n)
rounded = d.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# Result: Decimal('2.68')

# Different rounding modes
from decimal import ROUND_DOWN, ROUND_UP, ROUND_CEILING, ROUND_FLOOR

d.quantize(Decimal('0.01'), rounding=ROUND_DOWN)      # 2.67
d.quantize(Decimal('0.01'), rounding=ROUND_UP)        # 2.68
d.quantize(Decimal('0.01'), rounding=ROUND_CEILING)   # 2.68
d.quantize(Decimal('0.01'), rounding=ROUND_FLOOR)     # 2.67

String Formatting

from decimal import Decimal

# Format decimal - O(n)
d = Decimal('3.14159265')

# Using format() - O(n)
formatted = format(d, '.2f')  # '3.14' - O(4)

# Using f-string - O(n)
formatted = f"{d:.2f}"  # '3.14'

# As string - O(n)
s = str(d)  # '3.14159265'

Money Calculations

from decimal import Decimal, ROUND_HALF_UP

# Prices with Decimal
price = Decimal('19.99')
quantity = 3

# Multiplication - O(n²)
total = price * quantity  # Decimal('59.97')

# Tax calculation - O(n²)
tax_rate = Decimal('0.08')
tax = (total * tax_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# tax = Decimal('4.80')

final = total + tax
# final = Decimal('64.77')

Performance Considerations

Decimal vs Float

import time
from decimal import Decimal

# Float operations (fast but imprecise)
start = time.time()
result = 0.0
for _ in range(100000):
    result += 0.1
end = time.time()
print(f"Float time: {end - start}")  # Very fast
print(result)  # 10000.000000000002

# Decimal operations (slower but precise)
start = time.time()
result = Decimal('0')
for _ in range(100000):
    result += Decimal('0.1')
end = time.time()
print(f"Decimal time: {end - start}")  # Slower - O(n²)
print(result)  # Decimal('10000')

Precision Impact

from decimal import Decimal, getcontext

# Higher precision = slower operations
ctx = getcontext()

# Low precision (faster)
ctx.prec = 10
result = Decimal('1') / Decimal('3')  # O(10)

# High precision (slower)
ctx.prec = 100
result = Decimal('1') / Decimal('3')  # O(100)

# Each digit added increases computation

Caching Decimals

from decimal import Decimal

# Pre-compute common values - O(n) once
TAX_RATE = Decimal('0.08')
SHIPPING = Decimal('5.99')
DISCOUNT = Decimal('0.10')

# Reuse in loop - O(n) per operation, not O(n²)
prices = [Decimal(str(p)) for p in [10, 20, 30]]

for price in prices:
    total = price * (1 - DISCOUNT) + SHIPPING  # O(n)
    # Use tax_rate

Comparisons

from decimal import Decimal

# Equality - O(n) where n = digits
a = Decimal('10.00')
b = Decimal('10.0')
a == b  # True - O(4)

# Less than - O(n)
a < b   # False - O(4)

# Comparison with string (must convert) - O(n)
a == Decimal('10')  # True

# Comparison with float (works but imprecise)
a == 10.0  # True (generally, but be careful)

Context Management

Local Context

from decimal import Decimal, localcontext

# Global precision
getcontext().prec = 28

with localcontext() as ctx:
    # Local precision change
    ctx.prec = 10
    result = Decimal('1') / Decimal('3')  # O(10)
    print(result)  # Decimal('0.3333333333')

# Global precision restored
result = Decimal('1') / Decimal('3')
print(result)  # More digits - O(28)

Financial Calculations

Invoice Calculation

from decimal import Decimal, ROUND_HALF_UP, localcontext

def calculate_invoice(items, tax_rate):
    """Calculate invoice total with tax"""
    with localcontext() as ctx:
        ctx.prec = 10  # Sufficient for money

        # Sum items - O(n * m) where n = items, m = digits
        subtotal = sum((Decimal(str(price)) * qty for price, qty in items),
                      Decimal('0'))

        # Apply tax - O(m)
        tax = (subtotal * Decimal(str(tax_rate))).quantize(
            Decimal('0.01'), rounding=ROUND_HALF_UP)

        # Total - O(m)
        total = (subtotal + tax).quantize(
            Decimal('0.01'), rounding=ROUND_HALF_UP)

        return subtotal, tax, total

# Usage - O(n * m)
items = [(19.99, 1), (5.50, 2), (100, 0.5)]
subtotal, tax, total = calculate_invoice(items, 0.08)

String Representations

from decimal import Decimal

d = Decimal('123.456')

# As string - O(n)
str(d)          # '123.456'

# With engineering notation - O(n)
d.to_eng_string()  # '123.456'

# As tuple (sign, digits, exponent) - O(1)
d.as_tuple()    # DecimalTuple(sign=0, digits=(1,2,3,4,5,6), exponent=-3)

Special Values

from decimal import Decimal

# Special values
Decimal('Infinity')    # Positive infinity
Decimal('-Infinity')   # Negative infinity
Decimal('NaN')         # Not a number

# Create from float
import math
Decimal(math.inf)      # Decimal('Infinity')

# Operations with special values
d = Decimal('10')
d / Decimal('0')       # Raises InvalidOperation

Decimal('Infinity') + 5  # Decimal('Infinity')
Decimal('NaN') + 5       # Decimal('NaN')

Version Notes

  • Python 2.x: Decimal module available (limited support)
  • Python 3.x: Full Decimal support with many rounding modes
  • Python 3.8+: Improved performance and new operations
  • fractions - Rational number arithmetic (exact but slower)
  • math - Mathematical functions (uses floats)
  • numbers - Numeric abstract base classes

Best Practices

Do:

  • Use Decimal for financial calculations (exact results)
  • Create Decimal from strings, not floats
  • Set appropriate precision for your domain
  • Use quantize() for rounding money
  • Cache common Decimal values

Avoid:

  • Mixing Decimal and float operations (convert both to Decimal)
  • Creating Decimal from float: Decimal(0.1) ❌ use Decimal('0.1')
  • Excessive precision (slower without benefit)
  • Using Decimal for scientific computation (use float)
  • Forgetting that multiplication is O(n²)