I still remember the 3:00 AM pager alert from a legacy settlement engine I was maintaining—a system that had been running “fine” for years until a specific sequence of currency conversions triggered a rounding error. It wasn’t a dramatic crash; it was just a slow, silent leak of fractions of a cent that eventually turned a million-dollar ledger into a reconciliation disaster. Most tutorials make handling money in code look like a simple matter of picking a data type and calling it a day, but they never show you the part where the math starts bleeding money during a high-volume batch job.
I’m not here to give you a lecture on basic arithmetic or the “happy path” you’ll find in any bootcamp curriculum. Instead, I want to talk about the edge cases that actually keep backend developers awake at night. I’m going to share what I’ve learned from two decades of fixing broken payment rails, focusing on the uncomfortable realities of precision, scale, and the inevitable decay of old logic. We aren’t just writing functions; we are building systems where a single misplaced decimal point can break a company’s books.
Table of Contents
Why Floating Point Errors in Finance Kill Your Trust

It starts with a single cent. You’re running a batch process, everything looks green in the staging environment, and the logs show nothing unusual. But then a reconciliation report comes back from the accounting department, and the totals don’t match. You realize that a series of tiny, invisible discrepancies—the classic floating point errors in finance—have accumulated across ten thousand transactions.
In a tutorial, they tell you `float` or `double` is fine for “decimal numbers.” In production, those types are a liability. You’ll watch a simple interest calculation drift, turning a clean $1,200.50 into something like $1,200.4999999999998. When that value hits your database, you aren’t just dealing with a math error; you’re dealing with a broken audit trail. Once your ledger stops balancing, your users stop trusting the platform. You can’t just “fix” the math after the fact; you have to explain to a stakeholder why their balance is technically incorrect due to a hardware-level representation issue. It’s a conversation no backend developer wants to have.
Bigdecimal vs Float for Money Choosing Your Weapon

When you’re staring at a stack trace at 2:00 AM, the debate between BigDecimal vs float for money usually stops being theoretical. If you use a float, you’re essentially gambling with the binary representation of base-10 decimals. You might get away with it during your local unit tests, but once that code hits a high-volume production environment, those tiny inaccuracies compound. I’ve seen reconciliation reports fail because a series of micro-transactions drifted by a fraction of a cent, making it impossible to balance the books at the end of the day.
If you want to avoid the headache, you have two real choices. The first is moving toward arbitrary-precision arithmetic using classes like BigDecimal. It handles the heavy lifting for you, but it’s slower and more verbose, which can be a pain if you’re building something incredibly high-frequency. The second, and often my personal preference for simplicity, is using integers for currency. By treating everything as subunits—cents, millicents, or even smaller—you bypass the decimal problem entirely. You just have to be disciplined about your scaling logic, or you’ll end up losing track of where the decimal point actually lives.
Five ways to keep your ledger from becoming a crime scene
- Stop using floats for anything that touches a database. I don’t care how fast the calculation is; if you’re storing cents as a floating-point number, you’re just scheduling a midnight debugging session for six months from now when the rounding errors start compounding.
- Always define your currency context at the edge. A number without a currency code is just a digit waiting to be misinterpreted. If your function accepts `amount: 100` instead of `amount: 100, currency: ‘NOK’`, you’re inviting someone to accidentally treat Yen like Dollars.
- Use integers for the heavy lifting. My preferred pattern is to move everything into the smallest unit—subunits like cents or groshen—immediately upon entry. It’s much easier to reason about `1000` units of a cent than it is to manage `10.00` with a decimal point that might drift.
- Audit your rounding strategy before the first transaction hits. “Round half up” might work for a retail shop, but if you’re building a high-volume clearing system, the way you handle the .005 can result in massive discrepancies over millions of rows. Pick a standard and stick to it.
- Write your tests for the edge cases, not the success stories. Don’t just test that `2.00 + 2.00 = 4.00`. Test what happens when you subtract a discount that results in a negative balance, or when you divide a fee across three accounts and have a leftover fraction of a cent. That’s where the real bugs live.
The Cost of Getting It Wrong

Stop treating money like a generic number; if you use a float for a transaction, you aren’t just making a technical error, you’re building a debt that will eventually show up in your audit logs.
Pick your data type based on the math you actually have to do—use integers for simple counting or BigDecimals for complex interest calculations—but never, ever default to what feels “easy.”
Write your tests for the edge cases where the rounding happens, because the tutorial won’t tell you how much work it is to fix a corrupted ledger once the system is in production.
The Cost of the Happy Path
A tutorial will show you how to add two decimals and get a clean sum, but it won’t show you the 3:00 AM pager alert when a rounding error in a legacy loop starts leaking fractions of a cent across ten million transactions. In payments, if your math isn’t exact, you aren’t just writing bad code—you’re writing a liability.
Pernille Askeland
The Cost of Getting It Wrong
At the end of the day, handling money isn’t about writing clever algorithms; it’s about defensive engineering. We’ve covered why floating-point math is a non-starter for anything involving a ledger, and why choosing a robust type like BigDecimal (or its equivalent in your language) is the bare minimum for survival. You can’t just fix a rounding error after the database has already committed a thousand incorrect transactions. You have to architect for the error before it happens. If you treat every cent as a potential source of truth rather than just another variable, you’ve already done more than most.
I’ve spent two decades cleaning up the mess left behind by developers who thought “close enough” was acceptable for a balance sheet. My advice is simple: don’t let your legacy be a series of reconciliation reports that don’t add up. Code with the assumption that the math will eventually fail if you aren’t careful. When you prioritize precision over convenience, you aren’t just writing better backend logic—you are building the foundational trust that keeps a system alive. Go write your commit message, then go write the code that won’t break at 3:00 AM.
Frequently Asked Questions
If I move everything to integers (cents/micros) to avoid decimals entirely, how do I handle complex currency conversions without losing precision in the middle of the calculation?
Moving to integers is the right call, but it’s not a silver bullet. The trap is the intermediate step. If you’re converting USD to EUR, don’t just multiply and divide. You’ll lose those tiny fractions that accumulate into real discrepancies. Use a high-precision decimal type for the conversion factor itself, perform the math, and only then round back to your base integer unit. Scale your integers to micros if you really want to be safe.
How do I deal with rounding modes when a tax calculation results in a fraction of a cent—do I always round up, or does that create a discrepancy in my ledger?
If you pick a rounding mode at random, your ledger will eventually stop balancing. I’ve spent enough late nights chasing phantom cents to know that “rounding up” is a recipe for a reconciliation nightmare. You can’t just decide on the fly. You need a consistent strategy—usually `HALF_EVEN` (Banker’s Rounding) to minimize cumulative bias—and you must document it. If the tax law mandates a specific direction, your code must reflect that exact logic, or the auditors will find it first.
When I'm maintaining a legacy system that already used floats, what's the safest way to migrate the database schema to a decimal type without locking the tables or corrupting existing balances?
You can’t just run an `ALTER TABLE` on a production payments database; the lock will hang your service and the migration will time out. I’ve seen it happen. Instead, add a new column with the correct decimal type. Backfill it in small, throttled batches to avoid database contention. Once the new column matches the old one, update your application logic to write to both, then eventually switch reads to the new column.