Legacy Modernization
A One-Cent Bug: When Two Code Paths Disagree About Rounding
A customer's balance dropped by a penny. Nothing was lost, nothing was stolen, and the client was still right to escalate it.
Project Overview
A customer's balance dropped by a penny. Nothing was lost, nothing was stolen, and the client was still right to escalate it.
The report
A customer account on a multi-location education platform showed an expected balance of $614.40. After a week of lessons, it read $614.39.
The client's escalation carried a sharp observation: they expected it to drop again the following week. They had seen this pattern before, and they had correctly inferred it was recurring rather than one-off.
One cent. On a platform mid-migration from a legacy PHP system to a modern Node stack, with far larger issues in flight. It would have been easy to defer.
It was the right thing to chase — for a reason that had little to do with the cent.
Where the penny went
The account balance shown on the dashboard is not a stored number. It is recalculated:
balance = lessons due
+ outstanding invoices
− credits
A lesson contributes to that total through one of two routes, depending on its stage in the billing lifecycle. Before it's invoiced, it contributes via the lesson's own stored balance. After invoicing, it drops out of "lessons due" and contributes via the invoice instead.
Two routes. Two pieces of code. And they disagreed:
flowchart TD
accTitle: Rounding paths create a one-cent difference
accDescr: An uninvoiced lesson rounds after subtracting the discount and contributes 38.82 dollars, while an invoiced lesson rounds the discount first and contributes 38.81 dollars.
A["Lesson<br/>Gross: $40.00 · Discount: 3%"] --> B{Which path?}
B -->|"Not yet invoiced"| C["Raw discount: 1.185<br/>40.00 − 1.185 = 38.815<br/><b>Round last → $38.82</b>"]
B -->|"Invoiced"| D["Discount: 1.185 → <b>round first</b> → 1.19<br/>40.00 − 1.19<br/><b>= $38.81</b>"]
C --> E["Balance shows 38.82"]
D --> F["Balance shows 38.81"]
E --> G["Difference: $0.01<br/>per invoiced lesson"]
F --> G
style G fill:#5a1e1e,color:#fffOne path rounded the discount and then subtracted it. The other subtracted the raw discount and rounded the result. Same inputs, same intent, one cent apart.
So each week, as another lesson crossed from "due" to "invoiced," the account shed another penny. The client's prediction was exactly right, and their diagnosis — that this would keep happening — was better than the initial engineering read.
Why this class of bug is a modernization signature
Rounding disagreements are not interesting on their own. What makes this one worth writing about is why it existed.
In a decade-old system, the calculation of a discounted line total lives in one place, called from everywhere, refined over years of production edge cases. It is rarely elegant. It is usually correct, because every deviation from correct generated a complaint that got fixed.
A rewrite reimplements that logic. And it rarely reimplements it once — invoice generation needs it, the customer dashboard needs it, an edit-discount flow needs it, a report needs it. Each is written by a different person at a different time, each is individually reasonable, and each is tested against its own expectations.
Nothing tests that they agree with each other.
The migration didn't introduce a rounding error. It introduced a second opinion — and the discrepancy only became visible at the moment a record moved from one path's jurisdiction to the other's.
The fix
Small, once located: align invoice generation to round the final net total rather than the intermediate discount, and route it through the same shared rounding helper the lesson-total path already used.
The change was verified locally, then in a development environment, then scanned against production data to find already-affected records. Records that had not yet been invoiced would self-correct once the corrected path ran. Records already invoiced needed a targeted migration — and those were split by state, because correcting an unpaid invoice is a routine data fix while correcting a paid one touches money that has already moved.
The transferable lesson
If a business rule can be reached by more than one code path, the paths will eventually disagree. Not because anyone was careless — because "round the discount" and "round the total" are both defensible readings of the same requirement, and nothing in a normal code review flags the difference.
Two practices make this class of bug much less likely during a rewrite:
Extract shared arithmetic into one helper, and make using it the default. If there are four ways to compute a discounted line total in the codebase, three of them are wrong and nobody knows which three.
Test at the seams, not just within them. The strongest test here would not have checked either path in isolation. It would have asserted that a lesson's contribution to the balance is identical before and after invoicing — the transition is where the disagreement lives.
And when a client reports a one-cent discrepancy, treat it as a signal rather than a rounding nuisance. A penny that moves on a state transition is telling you two parts of your system have different beliefs about the same number. Next time, the gap may not be a penny.
Rewriting business-critical calculation logic? We help teams migrate financial rules with parity you can prove. Get in touch.
How we drive results
Turning Strategy into Measurable Business Impact
Our case studies reflect a consistent delivery model focused on outcomes, helping organizations modernize technology, reduce risk, and accelerate growth through practical, scalable solutions.
Outcome-Driven Strategy
Every engagement starts with clear business objectives, success metrics, and a roadmap aligned to real operational and financial outcomes.
Proven Execution Model
We apply proven frameworks, agile delivery, and industry best practices to execute complex initiatives with speed, quality, and predictability.
Secure & Scalable Delivery
Our solutions are built with security, compliance, and scalability at the core, ensuring long-term resilience and sustainable growth.