0%

56- Decimal Module

Precise decimal arithmetic for financial and scientific applications. No more floating point surprises. Control rounding, precision, and exact calculations.

You have seen the problem. 0.1 + 0.2 == 0.3 returns False. This is not a bug. It is floating point arithmetic. Computers store numbers in binary. Some decimal numbers cannot be represented exactly. For most applications, this is fine. The error is tiny. But for financial applications, accounting, currency calculations, and any situation where exact decimal representation matters, floating point errors are unacceptable. You cannot tell a customer that their account has $0.30000000000000004. The decimal module provides decimal floating point arithmetic with user-specified precision. It behaves like the decimal arithmetic you learned in school. Numbers are stored as decimals, not binary fractions. Operations are exact. This lesson covers the Decimal class, contexts for controlling precision, rounding modes, and when to use decimal over float.

🕯️ Magic Note

The decimal module is based on the General Decimal Arithmetic Specification, which is used in many financial and scientific systems. It is slower than binary floating point, but for applications where precision matters, it is the right tool.

Why Decimal? The Floating Point Problem
First, let us see the problem that decimal solves.

Python

from decimal import Decimal

# Floating point (binary) – has rounding errors

print(0.1 + 0.2) # 0.30000000000000004

print(0.1 + 0.2 == 0.3) # False

print(1.20 – 1.15) # 0.04999999999999982

# Decimal – exact decimal arithmetic

print(Decimal(“0.1”) + Decimal(“0.2”)) # 0.3

print(Decimal(“0.1”) + Decimal(“0.2”) == Decimal(“0.3”)) # True

print(Decimal(“1.20”) – Decimal(“1.15”)) # 0.05

⚠️ Never use floats for currency. Use Decimal or work in cents (integers). Floating point errors can accumulate and cause significant discrepancies in financial calculations.
Creating Decimal Objects
Create Decimal objects from strings, integers, or tuples. Avoid creating from floats.

Python

from decimal import Decimal

# From string (recommended)

d1 = Decimal(“0.1”)

d2 = Decimal(“3.141592653589793”)

d3 = Decimal(“123.45”)

# From integer

d4 = Decimal(42)

# From tuple (sign, digits, exponent)

d5 = Decimal((0, (1, 2, 3, 4, 5), -2)) # 123.45

# Avoid: creating from float (may inherit floating point error)

d6 = Decimal(0.1) # Not exactly 0.1! It is 0.100000000000000005551…

print(d6) # Decimal(‘0.1000000000000000055511151231257827021181583404541015625’)

# Always create from string for exact representation

d7 = Decimal(“0.1”) # Exactly 0.1

🕯️ Magic Note

Creating a Decimal from a float defeats the purpose. The float already has rounding error. That error becomes part of the Decimal. Always use strings for exact decimal values.

Decimal Operations
Decimal objects support standard arithmetic operations.

Python

from decimal import Decimal

a = Decimal(“10.50”)

b = Decimal(“3.25”)

print(f”Addition: {a + b}”) # 13.75

print(f”Subtraction: {a – b}”) # 7.25

print(f”Multiplication: {a * b}”) # 34.1250

print(f”Division: {a / b}”) # 3.230769230769230769230769231 (default precision)

print(f”Floor division: {a // b}”) # 3

print(f”Modulus: {a % b}”) # 0.75

print(f”Power: {a ** 2}”) # 110.25

# Comparison operators work as expected

print(a > b) # True

print(a == b) # False

Precision and Context
The getcontext() function returns the current context, which controls precision, rounding, and other settings.

Python

from decimal import Decimal, getcontext

# Check current precision (default is 28)

print(f”Default precision: {getcontext().prec}”)

# Change precision

getcontext().prec = 6

print(Decimal(“1”) / Decimal(“7”)) # 0.142857 (6 digits of precision)

getcontext().prec = 10

print(Decimal(“1”) / Decimal(“7”)) # 0.1428571429 (10 digits)

getcontext().prec = 28 # Reset to default

💡 Precision controls the number of significant digits, not decimal places. For financial calculations, you may want to set precision high enough to avoid rounding errors, then round to two decimal places for display.
Rounding Modes
Decimal provides several rounding modes to control how numbers are rounded.
Rounding ModeBehavior
ROUND_CEILINGRound towards Infinity (up for positive, up for negative)
ROUND_FLOORRound towards -Infinity (down for positive, down for negative)
ROUND_UPRound away from zero
ROUND_DOWNRound towards zero
ROUND_HALF_UPRound to nearest, ties away from zero
ROUND_HALF_DOWNRound to nearest, ties towards zero
ROUND_HALF_EVENRound to nearest, ties to even (banker’s rounding)
ROUND_05UPRound away from zero if last digit is 0 or 5

Python

from decimal import Decimal, getcontext, ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_CEILING, ROUND_FLOOR

value = Decimal(“0.125”)

# quantize() rounds to specified decimal places

print(f”Original: {value}”)

print(f”ROUND_HALF_UP: {value.quantize(Decimal(‘0.01’), rounding=ROUND_HALF_UP)}”) # 0.13

print(f”ROUND_HALF_DOWN: {value.quantize(Decimal(‘0.01’), rounding=ROUND_HALF_DOWN)}”) # 0.12

negative = Decimal(“-0.125”)

print(f”Negative: {negative}”)

print(f”ROUND_CEILING: {negative.quantize(Decimal(‘0.01’), rounding=ROUND_CEILING)}”) # -0.12 (up for negative)

print(f”ROUND_FLOOR: {negative.quantize(Decimal(‘0.01’), rounding=ROUND_FLOOR)}”) # -0.13 (down for negative)

# For financial applications, ROUND_HALF_EVEN is often used (reduces bias)

from decimal import ROUND_HALF_EVEN

print(f”ROUND_HALF_EVEN: {Decimal(‘0.125’).quantize(Decimal(‘0.01’), rounding=ROUND_HALF_EVEN)}”) # 0.12

print(f”ROUND_HALF_EVEN: {Decimal(‘0.135’).quantize(Decimal(‘0.01’), rounding=ROUND_HALF_EVEN)}”) # 0.14

🕯️ Magic Note

Banker’s rounding (ROUND_HALF_EVEN) rounds .5 to the nearest even digit. This eliminates the upward bias of always rounding .5 up. It is the default rounding for many financial systems and for Python’s round() function when dealing with ties.

The quantize() Method
quantize() rounds a Decimal to a fixed number of decimal places.

Python

from decimal import Decimal, ROUND_HALF_UP

price = Decimal(“19.999”)

tax = Decimal(“0.075”) # 7.5% tax

total = price * (1 + tax)

print(f”Raw total: {total}”) # 21.498925

# Round to 2 decimal places (currency)

rounded = total.quantize(Decimal(“0.01”), rounding=ROUND_HALF_UP)

print(f”Rounded total: {rounded}”) # 21.50

# Quantize to different precisions

value = Decimal(“123.4567”)

print(value.quantize(Decimal(“1”))) # 123

print(value.quantize(Decimal(“0.1”))) # 123.5

print(value.quantize(Decimal(“0.01”))) # 123.46

print(value.quantize(Decimal(“0.001”))) # 123.457

Working with Money (Currency)
A practical example of using Decimal for financial calculations.

Python

from decimal import Decimal, ROUND_HALF_UP, getcontext

# Set high precision for intermediate calculations

getcontext().prec = 28

class Money:

def __init__(self, amount):

if isinstance(amount, Decimal):

self.amount = amount

else:

self.amount = Decimal(str(amount))

def __add__(self, other):

result = self.amount + other.amount

return Money(result.quantize(Decimal(“0.01”), rounding=ROUND_HALF_UP))

def __sub__(self, other):

result = self.amount – other.amount

return Money(result.quantize(Decimal(“0.01”), rounding=ROUND_HALF_UP))

def __mul__(self, factor):

result = self.amount * Decimal(str(factor))

return Money(result.quantize(Decimal(“0.01”), rounding=ROUND_HALF_UP))

def __str__(self):

return f”${self.amount:.2f}”

# Usage

price = Money(19.99)

tax_rate = Decimal(“0.075”)

tax = price * tax_rate

total = price + tax

print(f”Price: {price}”) # $19.99

print(f”Tax: {tax}”) # $1.50

print(f”Total: {total}”) # $21.49

# Bulk purchase with discount

quantity = 3

subtotal = price * quantity

discount = Money(5.00)

final = subtotal – discount

print(f”3 items: {subtotal}”) # $59.97

print(f”After $5 discount: {final}”) # $54.97

Decimal vs Float: When to Use Which
Use Decimal When...Use Float When...
Financial and currency calculationsScientific and engineering calculations
You need exact decimal representationSpeed is critical (float is much faster)
Accounting, banking, tax computations3D graphics, game physics
When rounding behavior must be controlledWhen working with very large or very small numbers
When comparing decimal numbers for equalityWhen memory is limited (float uses less)
Numbers that represent precise human values (money, measurements)Performance-sensitive applications

Python

# Performance comparison (conceptual)

# Float operations: very fast (hardware accelerated)

# Decimal operations: slower (software implementation)

# But for financial code, correctness matters more than speed

price = Decimal(“19.99”) # Correct

# price = 19.99 # Dangerous for money

Common Operations Summary

Python

from decimal import Decimal, getcontext, ROUND_HALF_UP

# Create from string (most common)

d = Decimal(“123.45”)

# Basic arithmetic

d + Decimal(“10.00”)

d – Decimal(“5.00”)

d * Decimal(“2”)

d / Decimal(“3”)

# Round to 2 decimal places

rounded = d.quantize(Decimal(“0.01”), rounding=ROUND_HALF_UP)

# Change global precision

getcontext().prec = 50

# Square root

sqrt = d.sqrt()

# Compare

if d < Decimal(“100”):

print(“Less than 100”)

# Convert to string for output

output = str(d)

Common Mistakes with Decimal
  • Creating Decimal from float: Decimal(0.1) does not give Decimal(“0.1”)
  • Mixing Decimal and float in operations (TypeError)
  • Forgetting to quantize currency to 2 decimal places
  • Using Decimal when performance is critical and exactness is not needed
  • Not setting precision high enough for intermediate calculations
  • Assuming Decimal is always slower (it is, but correctness first)
Check Your Understanding
  • Why should you never use float for currency?
  • How do you create a Decimal representing exactly 0.1?
  • Write code to calculate 10% tax on $19.99 and round to 2 decimal places.
  • What does quantize(Decimal(“0.01”)) do?
  • What is banker’s rounding (ROUND_HALF_EVEN) and why is it used?
  • How do you change the global precision for Decimal operations?

⚡ Whisper

The float is fast. It is the workhorse of scientific computing, graphics, and machine learning. But for money, the float is a liar. It says 0.1 + 0.2 == 0.3 is False. It turns 1.20 – 1.15 into 0.04999999999999982. These are small lies. But lies compound. In accounting, small errors become large discrepancies. Auditors notice. Customers complain. The decimal module tells the truth. It computes exactly. It rounds predictably. It handles money honestly. Use it for financial code. Use it for tax calculations. Use it for any situation where human decimal expectations matter. The performance cost is worth it. Your customers deserve correct balances. Your accountants deserve exact numbers. In the world of money, truth matters. Use Decimal.

Related posts