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 |
Decimal.from_number(x) |
O(n) | O(n) | Convert from int/float/Decimal |
| 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 |
normalize() |
O(n) | O(n) | Remove trailing zeros |
sqrt() |
O(n²) | O(n) | Square root |
exp() / ln() / log10() |
O(n²) | O(n) | Transcendental functions |
Comparison (compare()) |
O(n) | O(1) | n = digits to compare |
compare_total() |
O(n) | O(1) | Compare with total ordering |
as_tuple() |
O(n) | O(n) | Return (sign, digits, exponent) |
as_integer_ratio() |
O(n) | O(n) | Return (numerator, denominator) |
to_eng_string() |
O(n) | O(n) | Engineering notation string |
to_integral_value() |
O(n) | O(n) | Round to integer |
copy_abs() / copy_negate() |
O(n) | O(n) | Copy with sign change |
copy_sign(other) |
O(n) | O(n) | Copy with other's sign |
is_finite() / is_infinite() |
O(1) | O(1) | Check special values |
is_nan() / is_qnan() / is_snan() |
O(1) | O(1) | Check NaN types |
is_signed() / is_zero() |
O(1) | O(1) | Check sign/zero |
is_normal() / is_subnormal() |
O(1) | O(1) | Check normalization |
number_class() |
O(1) | O(1) | Return classification string |
adjusted() |
O(1) | O(1) | Return adjusted exponent |
radix() |
O(1) | O(1) | Return 10 (base) |
same_quantum(other) |
O(1) | O(1) | Check same exponent |
scaleb(exp) |
O(n) | O(n) | Scale by power of 10 |
shift(n) / rotate(n) |
O(n) | O(n) | Shift/rotate digits |
logical_and/or/xor/invert |
O(n) | O(n) | Logical ops on digit strings |
fma(x, y) |
O(n²) | O(n) | Fused multiply-add |
max(other) / min(other) |
O(n) | O(1) | Return max/min |
next_plus() / next_minus() |
O(n) | O(n) | Next representable value |
next_toward(other) |
O(n) | O(n) | Next value toward other |
remainder_near(other) |
O(n²) | O(n) | IEEE remainder |
getcontext() |
O(1) | O(1) | Get current context |
setcontext(ctx) |
O(1) | O(1) | Set current context |
localcontext([ctx]) |
O(1) | O(1) | Context manager |
Context() |
O(1) | O(1) | Create context |
DecimalTuple |
O(1) | O(1) | Tuple of sign/digits/exponent |
DefaultContext / BasicContext / ExtendedContext |
O(1) | O(1) | Predefined contexts |
IEEEContext() |
O(1) | O(1) | IEEE 754 context |
| Signals/flags | O(1) | O(1) | Inexact, Rounded, Underflow, Overflow, Subnormal, Clamped |
| Exceptions | O(1) | O(1) | DecimalException, InvalidOperation, DivisionByZero, etc. |
| Rounding modes | O(1) | O(1) | ROUND_HALF_EVEN, ROUND_UP, etc. |
| Limits | O(1) | O(1) | MAX_PREC, MAX_EMAX, MIN_EMIN, MIN_ETINY |
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
Related Modules¶
- 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)❌ useDecimal('0.1')✅ - Excessive precision (slower without benefit)
- Using Decimal for scientific computation (use float)
- Forgetting that multiplication is O(n²)