The symptom
A customer buys three things. The screen says 700.01 when every price on the shelf ends in a round figure. At closing time the report says the drawer should hold 48,312.40, the count says 48,312.38, and nobody took two cents. An account shows a balance of 0.00, yet it keeps appearing in the list of amounts owed.
Each of these is the software doing arithmetic the wrong way, and it gets a little worse with every bill the business rings up.
Where the missing cent comes from
Computers store most numbers in binary. Whole numbers are exact, but many ordinary decimal amounts, even something as simple as 0.10, cannot be written exactly in binary. The software keeps the nearest value it can and hopes the difference never shows.
How near it gets depends on how many bits the program uses. Older packages often store amounts as 32-bit floating point numbers, which hold only about seven significant digits:
| Amount typed | What a 32-bit float stores | What prints |
|---|---|---|
| 700.01 | 700.010009765625 | 700.01 |
| 1,234,567.89 | 1,234,567.875 | 1,234,567.88 |
The first row looks harmless because it still prints correctly. But the extra 0.000009765625 is real, and it travels into every total, balance and report that amount touches. The second row is a cent wrong before anybody adds anything to it: at that size a 32-bit float cannot hold the cents at all.
Modern 64-bit numbers push the problem further out without removing it. Add 0.10 ten times and the answer is 0.9999999999999999, not 1.00. It prints as 1.00, so everything looks fine until the software checks whether a bill is fully paid by comparing the two figures, and decides it is not.
Percentages create half cents
Discounts and taxes are percentages, and a percentage of an ordinary price rarely lands on a whole cent. 12.5% of 45.50 is 5.6875. Something has to decide where that fraction goes, and when nobody decides it deliberately, two parts of the same program decide differently.
Take one bill with three items and a 10% discount:
| Item price | 10% exactly | Rounded on each line |
|---|---|---|
| 12.45 | 1.245 | 1.25 |
| 7.35 | 0.735 | 0.74 |
| 3.15 | 0.315 | 0.32 |
| 22.95 | 2.295 | 2.31 |
Round each line and the discount is 2.31. Take 10% of the bill total and round once, and it is 2.30. Both methods are defensible. The trouble starts when the bill uses one and the day report uses the other, because then they disagree by a cent on this bill, and by far more than a cent by the end of a busy month.
Splitting money loses the leftover
A balance of 1,000.00 split into three instalments is 333.33 three times, which adds up to 999.99. The missing cent has not gone anywhere you can see. It was simply never given to anybody. The same thing happens when a delivery's transport cost is spread across its items, or when one payment is shared across several open bills.
The four rules that stop it
- Store money as whole cents. 700.01 is kept as the whole number 70001. Whole numbers are exact in binary, so nothing drifts, however many bills are added together.
- Round in one place, by one rule. Decide with your accountant whether tax and discounts round per line or per bill, and how halves are treated. Write that rule once, and make every screen and every report use it.
- Hand out the leftover cent on purpose. Split 1,000.00 into three and the instalments are 333.34, 333.33 and 333.33. The remainder is allocated by a fixed rule, never dropped.
- Refuse to save anything that does not balance. A sale writes its ledger lines together, and debits must equal credits to the cent before the save is allowed. If they do not, the program stops and says so, instead of quietly rounding the difference away.
None of this is exotic. It is the difference between software built for money and software built for numbers.
Test your own software in ten minutes
You do not need to read any code to find out how your current system behaves. Try these on a training copy, or on a test bill you cancel afterwards:
- Ring up 12.45, 7.35 and 3.15 with a 10% bill discount, then check whether the bill and the day report agree on the discount.
- Split a 1,000.00 balance into three payments and check that the three add back to exactly 1,000.00.
- Add up one day's bills in a spreadsheet and compare the total with the day report.
- Find an account showing 0.00 and check whether it still appears in the list of amounts owed.
- Ask your supplier one direct question: how does the database store an amount of money? The answer you want to hear is "as whole cents".
What it looked like in a real shop
When we rebuilt the counter system for a retail shop in the Central Province, the old package stored amounts as 32-bit floats. Bills came out at 700.01, and balances that should have been zero sat at 0.000009765, which is exactly the leftover in the first table above.
The replacement holds every amount as whole cents. Moving across meant converting every stored figure and checking the converted totals against the old ones before the shop relied on the new system. It is slow, careful work, and it is the only way to be sure the history you carry forward is the history you actually had.
More on how we build money handling: how we work. To see whole-cent arithmetic in action, split a payment in the live POS demo.