Decimal Module — Core Concepts

Why Floats Are Imprecise

IEEE 754 floating-point numbers store values in binary. Just as 1/3 can’t be exactly written in base-10 (0.333…), many base-10 fractions can’t be exactly represented in base-2. The number 0.1 in binary is a repeating fraction — Python stores an approximation.

For most applications, this is invisible. But financial calculations compound these errors:

>>> sum([0.1] * 10)
0.9999999999999999  # Not 1.0

The decimal module uses base-10 arithmetic, so 0.1 is exactly 0.1.

Creating Decimals

Always create from strings, not floats:

from decimal import Decimal

good = Decimal("0.1")         # Exact: 0.1
bad = Decimal(0.1)            # 0.1000000000000000055511151231257827021181583404541015625
also_good = Decimal(1) / Decimal(10)  # Exact: 0.1

Creating from a float bakes in the float’s imprecision. This is the most common beginner mistake.

Precision and Context

The decimal module uses a thread-local context that controls precision (significant digits) and rounding behavior:

from decimal import Decimal, getcontext

getcontext().prec = 6
print(Decimal("1") / Decimal("7"))  # 0.142857

getcontext().prec = 28  # Default
print(Decimal("1") / Decimal("7"))  # 0.1428571428571428571428571429

Precision applies to arithmetic operations, not to the stored value. Decimal("3.14159265358979") stores all those digits regardless of the context precision.

Rounding Modes

Python’s decimal module supports eight rounding modes. The most important:

ModeBehaviorExample (2.5 → ?)
ROUND_HALF_EVENRound to nearest even (banker’s rounding)2
ROUND_HALF_UPRound 0.5 up (school math)3
ROUND_DOWNTruncate toward zero2
ROUND_CEILINGRound toward positive infinity3
ROUND_FLOORRound toward negative infinity2

Banker’s rounding (ROUND_HALF_EVEN) is the default and reduces systematic bias in large datasets. Financial regulations in many countries require it.

from decimal import ROUND_HALF_UP

price = Decimal("19.995")
print(price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))  # 20.00
print(price.quantize(Decimal("0.01")))  # 20.00 (HALF_EVEN, same here)

price2 = Decimal("19.985")
print(price2.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))  # 19.99
print(price2.quantize(Decimal("0.01")))  # 19.98 (HALF_EVEN rounds to even)

quantize: Fixed-Point Formatting

quantize() is how you enforce a specific number of decimal places:

amount = Decimal("42.1")
print(amount.quantize(Decimal("0.01")))  # 42.10
print(amount.quantize(Decimal("1")))      # 42
print(amount.quantize(Decimal("0.001")))  # 42.100

The argument is an exponent template, not a value. Decimal("0.01") means “two decimal places.”

Decimal vs Float: When to Choose

Use CaseRecommendedWhy
Money, accountingDecimalExact representation required
Scientific computingfloatSpeed matters, approximation is OK
User-facing pricesDecimalRounding must be predictable
Machine learningfloat/numpyGPU-optimized, precision less critical
Tax calculationsDecimalRegulatory compliance demands exactness

Common Misconception

“Decimal is always more accurate than float.” Not exactly. Decimal is more predictable for base-10 fractions. For irrational numbers like π or √2, both float and Decimal are approximations. Decimal’s advantage is representing human-written numbers (prices, percentages, tax rates) exactly.

Traps and Signals

The decimal context can raise exceptions (traps) for suspicious operations:

  • DivisionByZero — dividing by zero
  • Overflow — result too large
  • InvalidOperation — undefined math (like Decimal('NaN') + 1)
  • Inexact — result required rounding

By default, most traps are off. In financial code, enabling Inexact can catch rounding surprises early.

One thing to remember: Use Decimal when humans will read the numbers and expect exact base-10 behavior. Create from strings, use quantize() for fixed-point formatting, and pick the right rounding mode for your domain.

pythonstandard-librarynumbers

See Also

  • Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
  • Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
  • Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
  • Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
  • Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.