decimal
The decimal module provides support for fast correctly rounded decimal floating-point arithmetic. It solves the precision problems inherent in binary floating-point (float) by representing numbers exactly. For example, 0.1 + 0.2 equals 0.3 with Decimal but equals 0.30000000000000004 with float. This makes it essential for financial applications, currency calculations, and any domain where exact decimal representation matters.
Unlike binary floating-point, decimal floating-point stores the exact digits you provide. The module also lets you control precision, rounding modes, and exponent ranges through context objects.
Syntax
import decimal
# Common imports
from decimal import Decimal, getcontext, localcontext
from decimal import ROUND_HALF_EVEN, ROUND_DOWN, ROUND_UP, ROUND_CEILING
Creating Decimal Objects
The Decimal class accepts integers, strings, floats, or tuples. Each input type behaves differently.
from decimal import Decimal
# From integer — exact conversion
Decimal(10)
# Decimal('10')
# From string — exact conversion, preferred method
Decimal('3.14')
# Decimal('3.14')
# From float — converts the exact binary representation
Decimal(3.14)
# Decimal('3.140000000000000124344978758017532527446746826171875')
# From tuple — (sign, digits, exponent)
Decimal((0, (1, 4, 1, 4), -3))
# Decimal('1.414')
# Special values
Decimal('Infinity')
Decimal('-Infinity')
Decimal('NaN') # quiet NaN
Decimal('sNaN') # signaling NaN
The string approach is generally preferred for user input because it gives exact control over the value. Float conversion captures the binary approximation, which defeats the purpose of using Decimal.
Basic Arithmetic
Decimal supports all standard arithmetic operators. The key advantage is exact results for operations that would otherwise lose precision.
from decimal import Decimal
# Addition — no floating-point artifacts
a = Decimal('1.1')
b = Decimal('2.2')
print(a + b)
# Decimal('3.3')
# Multiplication preserves significant figures
print(Decimal('1.30') * Decimal('1.20'))
# Decimal('1.5600')
# Division
print(Decimal('10') / Decimal('3'))
# Decimal('3.333333333333333333333333333')
# Power
print(Decimal('2') ** Decimal('0.5'))
# Decimal('1.414213562373095048801688724')
The behavior of % and // differs from integers. The remainder keeps the sign of the dividend, not the divisor, which matches the decimal arithmetic specification rather than integer arithmetic.
print(Decimal(-7) % Decimal(4))
# Decimal('-3')
print(Decimal(-7) // Decimal(4))
# Decimal('-1')
Precision and Rounding
The context controls precision and rounding. getcontext() returns the current thread’s context with a default precision of 28 digits.
from decimal import Decimal, getcontext, FloatOperation
# Default precision
ctx = getcontext()
print(ctx.prec) # 28
print(ctx.rounding) # ROUND_HALF_EVEN
# Set precision for a calculation
getcontext().prec = 6
print(Decimal(1) / Decimal(7))
# Decimal('0.142857')
The precision only affects arithmetic operations, not the storage of already-created Decimal values. A Decimal(‘3.00000’) keeps all five trailing zeros regardless of context precision.
Working with Context
getcontext() and setcontext()
getcontext() accesses the current thread’s context. setcontext() replaces it with a different context object.
from decimal import getcontext, setcontext, Context
# View current context
ctx = getcontext()
print(ctx)
# Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[Overflow, DivisionByZero, InvalidOperation])
# Create and set a custom context
custom = Context(prec=50, rounding=ROUND_HALF_EVEN)
setcontext(custom)
localcontext()
The localcontext() function creates a temporary context for a code block, then restores the previous context when done. This is the cleanest way to change precision locally.
from decimal import Decimal, localcontext
# Default precision outside the block
print(Decimal(1) / Decimal(7))
# Decimal('0.1428571428571428571428571429')
# Higher precision inside the block
with localcontext() as ctx:
ctx.prec = 50
print(Decimal(1) / Decimal(7))
# Decimal('0.14285714285714285714285714285714285714285714285714')
# Back to default outside
print(Decimal(1) / Decimal(7))
# Decimal('0.1428571428571428571428571429')
You can also pass precision directly to localcontext():
with localcontext(prec=100):
result = Decimal(1) / Decimal(7)
Rounding Modes
The decimal module provides eight rounding modes:
| Mode | Description |
|---|---|
ROUND_HALF_EVEN | Round to nearest, ties to even (default) |
ROUND_HALF_UP | Round to nearest, ties away from zero |
ROUND_HALF_DOWN | Round to nearest, ties toward zero |
ROUND_UP | Always round away from zero |
ROUND_DOWN | Always round toward zero |
ROUND_CEILING | Round toward positive infinity |
ROUND_FLOOR | Round toward negative infinity |
ROUND_05UP | Round zero or five, away from zero |
The quantize() Method
The quantize() method rounds a Decimal to a fixed exponent. It’s the standard tool for currency formatting.
from decimal import Decimal, ROUND_HALF_UP
# Round to 2 decimal places
price = Decimal('7.325')
print(price.quantize(Decimal('.01')))
# Decimal('7.33')
# Round to whole number
print(price.quantize(Decimal('1'), rounding=ROUND_HALF_UP))
# Decimal('8')
The exponent in the target determines the precision. .01 means two decimal places, .001 means three, and so on.
Common Patterns
Financial Calculations
from decimal import Decimal, localcontext, ROUND_HALF_EVEN
# Calculate total with tax
subtotal = Decimal('99.99')
tax_rate = Decimal('0.0825') # 8.25%
tax = (subtotal * tax_rate).quantize(Decimal('.01'))
total = (subtotal + tax).quantize(Decimal('.01'))
print(f"Subtotal: ${subtotal}")
print(f"Tax: ${tax}")
print(f"Total: ${total}")
# Subtotal: $99.99
# Tax: $8.25
# Total: $108.24
Precision Control for Scientific Calculations
from decimal import Decimal, localcontext
def calculate_with_precision(prec):
with localcontext(prec=prec):
# High precision calculation
pi = sum(
Decimal(1) / Decimal(16) ** k *
(Decimal(4) / Decimal(8*k + 1) -
Decimal(2) / Decimal(8*k + 4) -
Decimal(1) / Decimal(8*k + 5) -
Decimal(1) / Decimal(8*k + 6))
for k in range(100)
)
return +pi # Unary + applies current context precision
print(calculate_with_precision(10))
# Decimal('3.141592654')
print(calculate_with_precision(50))
# Decimal('3.14159265358979323846264338327950288419716939937510')
Avoiding Float Mixing
The FloatOperation signal warns when you accidentally mix Decimal with float. Enable the trap to catch these bugs early.
from decimal import Decimal, getcontext, FloatOperation
# Enable the trap
getcontext().traps[FloatOperation] = True
# This raises an exception
try:
Decimal(3.14)
except FloatOperation as e:
print(f"Caught: {e}")
# Caught: decimal.FloatOperation: [<class 'decimal.FloatOperation'>]
Errors
Several signals can occur during decimal operations:
- InvalidOperation: Invalid operand (division by zero, conversion error)
- DivisionByZero: Division by zero (if trapped)
- Inexact: Rounding occurred during an operation
- Rounded: A number was rounded to fit precision
- Overflow: Exponent exceeded Emax
- Underflow: Result is too small to represent
- FloatOperation: Decimal and float were mixed
By default, only Overflow, DivisionByZero, and InvalidOperation are trapped as exceptions.
See Also
- fractions::fractions — for rational number arithmetic
- built-in::float — built-in binary floating-point
- built-in::int — arbitrary precision integers