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:
| Mode | Behavior | Example (2.5 → ?) |
|---|---|---|
ROUND_HALF_EVEN | Round to nearest even (banker’s rounding) | 2 |
ROUND_HALF_UP | Round 0.5 up (school math) | 3 |
ROUND_DOWN | Truncate toward zero | 2 |
ROUND_CEILING | Round toward positive infinity | 3 |
ROUND_FLOOR | Round toward negative infinity | 2 |
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 Case | Recommended | Why |
|---|---|---|
| Money, accounting | Decimal | Exact representation required |
| Scientific computing | float | Speed matters, approximation is OK |
| User-facing prices | Decimal | Rounding must be predictable |
| Machine learning | float/numpy | GPU-optimized, precision less critical |
| Tax calculations | Decimal | Regulatory 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 zeroOverflow— result too largeInvalidOperation— undefined math (likeDecimal('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.
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.