Python Decimal for money versus floating-point arithmetic
In this article (6 sections)
For monetary calculations that must reconcile to decimal source amounts, use Decimal constructed from validated text or integer minor units. A float remains useful for many statistical calculations, but its binary representation should not quietly define an invoice's rounding policy.
The important decision is more specific than choosing a Python type: define currency, scale, rounding mode and the point at which rounding occurs. Those decisions determine whether two independently produced reports agree.
Demonstrate the representation difference
Run this standard-library example in the Python reporting lab:
from decimal import Decimal, ROUND_HALF_UP, localcontext
assert 0.1 + 0.2 != 0.3
assert Decimal("0.1") + Decimal("0.2") == Decimal("0.3")
assert Decimal(0.1) != Decimal("0.1")
with localcontext() as context:
context.prec = 28
rounded = Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
assert rounded == Decimal("2.68")
print("Decimal input and explicit rounding verified")Constructing Decimal from a float preserves the float's represented value; it does not recover the original decimal text. The Decimal documentation describes construction, arithmetic contexts and quantize. The assertions above make the distinction observable without relying on how a notebook formats a number.
Reconcile an actual fixture
The synthetic lab contains eleven input records, including an identical replay and three rejected records. Its January report uses four valid paid orders: INR 19.00, 5.00, 10.00 and 13.50.
from decimal import Decimal
from pathlib import Path
from report import load_orders, paid_summary
with Path("raw_orders.csv").open(encoding="utf-8-sig", newline="") as handle:
orders, rejected, replays, raw_count = load_orders(handle)
summary = paid_summary(orders, "2026-01")
assert summary["paid_amount_inr"] == "47.50"
assert sum((Decimal(v) for v in ["19.00", "5.00", "10.00", "13.50"]), Decimal("0.00")) == Decimal("47.50")
print(summary)Decimal alone does not reject a duplicate order or a pending payment. The amount is correct because validation, identity rules, date boundaries and status filtering accompany the numeric type.
Choose where to round
Consider three synthetic fee components of INR 0.005 each. Rounding every component to two decimal places with half-up gives INR 0.03 in total. Summing first and rounding the total gives INR 0.02.
from decimal import Decimal, ROUND_HALF_UP
parts = [Decimal("0.005")] * 3
unit = Decimal("0.01")
per_line = sum((x.quantize(unit, rounding=ROUND_HALF_UP) for x in parts), Decimal("0.00"))
at_total = sum(parts, Decimal("0.00")).quantize(unit, rounding=ROUND_HALF_UP)
assert (per_line, at_total) == (Decimal("0.03"), Decimal("0.02"))
print(per_line, at_total)Neither result becomes the business rule merely because Python produced it. Obtain the documented rounding policy and test it at the specified grain. This hypothetical example explains arithmetic; it is not a tax or invoicing rule.
When integer minor units help
If every accepted value is already an exact number of paise, store 19.00 as 1900 and sum integers. Conversion between major and minor units must still validate scale. Multiplying an arbitrary binary float by 100 and truncating is not a safe general conversion method.
Integer minor units also need currency metadata. One integer column cannot describe both a count and an amount, and currencies should not be summed together without an explicit conversion contract. Percentages, exchange conversions and allocations may require intermediate precision beyond the final display scale.
Review the handoff
The lab serializes monetary totals as decimal strings in JSON so a consumer does not immediately reintroduce binary floating-point interpretation. A receiving system should document how it parses those strings. If the output is CSV, include currency and unit definitions in its data dictionary.
Exercise: allocate INR 10.00 across three recipients. Produce minor-unit allocations whose sum remains exactly INR 10.00, specify who receives the remainder, and test that reordering recipients does not accidentally change an intended priority rule.
NeuraPath's Data Analytics with Generative AI course links Python calculations to report reconciliation. This exercise develops the habit of making numerical policy explicit before a polished dashboard hides the discrepancy.
Continue learning
This article is part of the Python foundations for analysts sequence. Use the neighbouring tasks when you need the prerequisite or the next application.
- Review the prerequisite or neighbouring task in Read CSV files without corrupting customer identifiers.
- Continue with Python datetime: distinguish dates, local times and instants.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI