Low-Level Design (Object-Oriented Design) Module 5 – Modelling lifecycles, time, money and rules
Money arithmetic: paise, rounding and splits
Keep money exact in integer paise or decimals, name each rounding rule and where it applies, and split totals so that no paisa is lost or invented.
What you will learn
- Represent money as integer minor units or decimal values that carry a currency
- Apply a named rounding mode at a named place, per line or per invoice
- Split amounts by allocation so that the parts always add up to the total
- Store tax rates and fees in configuration with the dates they apply from
Before you start
What you’ll learn · Before you start
What you will learn
- Represent money as integer minor units or decimal values that carry a currency
- Apply a named rounding mode at a named place, per line or per invoice
- Split amounts by allocation so that the parts always add up to the total
- Store tax rates and fees in configuration with the dates they apply from
Before you start
On this page
Money in code needs three decisions that binary floating-point numbers make silently, and wrongly. The first is the representation: an amount is a whole number of minor units, such as paise or cents, or a decimal value, and it always carries its currency. The second is rounding: when a result falls between two paise, a named rounding mode decides, at a named place, per line or per invoice. The third is splitting: when a total is divided, the paise left over are handed out by a fixed rule, so the parts add back up to the total. Tax rates and fees are inputs to these rules, kept as configuration with the dates they apply from.
Why a float loses paise, and what keeps them
A binary float stores fractions as sums of halves, quarters and eighths, so 0.1, 0.2 and most amounts of paise or cents are kept as the nearest binary value, not exactly. The errors are tiny, but they add up, and they surface where money is least forgiving: in totals that compare unequal and in splits that do not add up. A short script shows both, beside the integer and decimal versions that keep every paisa:
"""0.1 + 0.2 in binary floating point, then 1,000 rupees split three ways: as a float, as shares rounded to
whole paise one by one, and as paise allocated so that the shares add up to the bill."""
from decimal import Decimal
def rupees(paise):
"""Formats a whole number of paise without ever making a float."""
return f"{paise // 100:,}.{paise % 100:02d}"
print("0.1 + 0.2 as floats: ", 0.1 + 0.2)
print("10 + 20 paise as integers: ", 10 + 20)
print("Decimal('0.1') + Decimal('0.2'):", Decimal("0.1") + Decimal("0.2"))
print()
bill = 100_000 # 1,000 rupees, in paise
print("1,000 rupees / 3 as a float: ", 1000 / 3)
each = round(bill / 3) # every share rounded to whole paise on its own
print(f"each share rounded to paise: {rupees(each)} x 3 = {rupees(each * 3)}, one paisa short")
base, left = divmod(bill, 3)
shares = [base + 1 if i < left else base for i in range(3)]
print(f"allocated: {' + '.join(map(rupees, shares))} = {rupees(sum(shares))}") Output
0.1 + 0.2 as floats: 0.30000000000000004
10 + 20 paise as integers: 30
Decimal('0.1') + Decimal('0.2'): 0.3
1,000 rupees / 3 as a float: 333.3333333333333
each share rounded to paise: 333.33 x 3 = 999.99, one paisa short
allocated: 333.34 + 333.33 + 333.33 = 1,000.00
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 thousand_three_ways.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The float sum printed 0.30000000000000004. The split shows a problem that remains even with exact numbers: 1,000 rupees do not divide into three equal amounts of whole paise, so rounding each share gives 333.33 three times, 999.99 in all, and a paisa disappears. Allocation fixes it by giving the leftover paisa to one share, by a rule anyone can predict.
Where each money decision lives
Text description of the diagram
Six steps run from top to bottom.
- An amount arrives from a price list, a form or an API as text, such as "1,250.50", together with its currency.
- It is parsed without passing through a float, into whole minor units such as 125050 paise in a 64-bit integer, or into a decimal such as Decimal("1250.50").
- Additions, multiplications and rates are computed exactly, with nothing rounded yet. Rates and fees come in from configuration, each with the day it applies from.
- The result is rounded once, where the business rule says (per line or per invoice), with a named mode such as half-up or half-even.
- When a total is split, the leftover paise are handed out by a fixed rule, so the parts add up to the total.
- Only at the edge is the amount formatted for people, such as "1,250.50 INR".
Each decision has its place on that path, and each tool has one job:
- Integer minor units hold an amount as a whole count of paise or cents, so sums and comparisons are exact.
- Decimal types, such as Python’s
decimaland Java’sBigDecimal, store base-10 digits with no binary error and apply a rounding mode only when the code asks for one. - Rounding modes settle ties such as half a paisa: half-up rounds them away from zero, half-even to the even neighbour.
- The place of rounding, per line or once per invoice, is a business rule that can move a total by paise.
- Allocation hands out the leftover paise of a split one at a time in a fixed order, so the parts add up.
- Currency checks refuse to add amounts in different currencies; converting is a separate, explicit step.
- Rates as configuration keep tax percentages and fees as data with the day they apply from.
- Safe integers matter in JavaScript, whose numbers are doubles: whole paise stay exact only up to 2⁵³ − 1.
The same invoice three ways: floats, paise and decimals Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Rounding is a business rule: the mode, and where it applies
- Splitting without losing a paisa
- Currency belongs to the amount
- JavaScript: safe integers and BigInt
- Rates as configuration, with the day they apply from
- Practise: an invoice with dated rates and one rounding
- An invoice total with dated tax rates and one rounding
- Check yourself
- Interview questions
- Why should a payment system not keep amounts in a binary floating-point type such as double?
- A bill of 1,000 rupees is shared equally by three friends, and another of 100 rupees by the ratio 3 : 2 : 2. Design a split function whose parts always add up to the bill, and say who receives each leftover paisa and why.
- Your billing service shows 18.00 rupees of tax on an invoice line of 100.05 rupees, but a customer's spreadsheet shows 18.01. The line carries 9 % CGST and 9 % SGST. Explain the difference, and decide where your system should round and how it should show the result.
- A Node.js service keeps balances as JavaScript numbers in rupees, such as 1234.5. Some totals end in long strings of digits, and a large merchant's monthly settlement is a few paise off. What is wrong, what do you change in the code and in the API, and how do you migrate safely?
- Key takeaways
7 runnable examples (JavaScript, Python, allocate.mjs, allocate.py) · 1 exercise with sample tests · 3 interview answers · about 21 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Read a complete sample first: Low-level design: parking lot (the model)
References
- decimal: decimal fixed-point and floating-point arithmetic (rounding modes) (Python Software Foundation)
- Built-in functions: round() (Python Software Foundation)
- Floating-point arithmetic: issues and limitations (Python Software Foundation)
- fractions: rational numbers (Python Software Foundation)
- Java SE 25 API: java.math.BigDecimal (Oracle)
- Java SE 25 API: java.util.Currency (Oracle)
- ISO 4217: currency codes (International Organization for Standardization)
- BigInt (MDN Web Docs (Mozilla))
- Number.MAX_SAFE_INTEGER (MDN Web Docs (Mozilla))
- IEEE Standard for Floating-Point Arithmetic (IEEE 754) (IEEE)
- CGST Act, section 9: levy and collection (Central Board of Indirect Taxes and Customs (CBIC))
- CGST Act, section 170: rounding off of tax (Central Board of Indirect Taxes and Customs (CBIC))
- CGST Rules, rule 46: tax invoice (Central Board of Indirect Taxes and Customs (CBIC))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress