decimal

Updated March 13, 2026 · Modules
numbers arithmetic precision stdlib

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:

ModeDescription
ROUND_HALF_EVENRound to nearest, ties to even (default)
ROUND_HALF_UPRound to nearest, ties away from zero
ROUND_HALF_DOWNRound to nearest, ties toward zero
ROUND_UPAlways round away from zero
ROUND_DOWNAlways round toward zero
ROUND_CEILINGRound toward positive infinity
ROUND_FLOORRound toward negative infinity
ROUND_05UPRound 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