Sooner or later, most developers hit the moment where adding two simple decimals produces a bizarre trailing result, or a total is off by a fraction of a cent. It looks like a bug in the language, but it is actually a fundamental property of how computers store decimal numbers — and it is the reason you should never hold money in an ordinary floating-point value.
Understanding why saves you from a class of costly, embarrassing errors.
Why 0.1 + 0.2 misbehaves
Floating-point numbers store values in binary, and many perfectly ordinary decimal fractions cannot be represented exactly in binary — much as one-third cannot be written exactly in decimal. The computer stores the closest approximation it can, and those tiny approximation errors accumulate through arithmetic, producing results that are almost, but not exactly, right.
This is not a defect to be fixed; it is an inherent trade-off of a format designed for a huge range of values, not exact decimals.
Why money is the danger zone
For scientific measurements, minuscule rounding is usually fine. For money, it is not: fractions-of-a-cent errors accumulate across many transactions, totals fail to reconcile, and users notice when a bill is a penny wrong. Financial calculations demand exactness that floating-point simply does not promise.
The failure is insidious because small cases often look correct, and the errors only surface at scale or in edge cases — exactly where money matters most.
What to do instead
The standard solutions are to work in the smallest whole unit — storing amounts as integer cents rather than fractional currency — or to use a dedicated decimal type designed for exact base-ten arithmetic, which many languages provide. Both avoid the binary-approximation problem entirely.
The rule of thumb is simple and worth memorising: floating-point is for measurements, not money. Store currency as integers or a proper decimal type, and this whole category of bug disappears.
Integer cents: the simplest fix, and where it still needs care
The most common practical fix for storing money is refusing to store it as a fractional amount at all: store every value as an integer count of the smallest currency unit — cents for dollars, pence for pounds — and only convert to a decimal display format at the moment it is shown to a user. Addition and subtraction on integers have no rounding error at all, which eliminates the entire class of drift this cluster of articles is built around, but the approach still needs explicit, deliberate handling wherever a genuinely fractional operation occurs, like splitting a total three ways or applying a percentage discount, since those operations can still produce a fractional cent that has to be rounded somewhere, and where that rounding happens is a real business decision, not an incidental implementation detail.
Why the database column type matters as much as the application code
Fixing the application layer to handle money correctly while the database still stores it in a floating-point column simply relocates the bug rather than closing it — a `FLOAT` or `DOUBLE` column type applies the same binary approximation at the storage layer regardless of how carefully the surrounding application code behaves, and a value that entered the database via one correct path can still come out subtly wrong the next time it is read. The fix is a `DECIMAL` or `NUMERIC` column type, which most relational databases support specifically for this purpose, storing an exact decimal representation rather than a binary approximation, or an integer column if the application layer has already committed to the integer-cents approach described above.
Rounding rules are a specification decision, not a coding one
Given that some operations on money genuinely cannot avoid producing a fractional smallest unit, deciding how to round it — round half up, round half to even, always round down and keep the remainder as a separate line item — is a decision with real financial and, in some jurisdictions, real legal implications, and it needs to be made explicitly and documented, ideally by whoever actually owns the product or financial requirements, rather than left to whatever a particular library or language happens to default to. Two systems that handle rounding differently can disagree on the exact final total for the same transaction, which is precisely the kind of discrepancy that turns into a support ticket or a reconciliation headache months later.
Why currency and monetary value libraries exist at all
Beyond the pure numeric representation problem, money has several other properties that a bare number, integer or otherwise, does not capture on its own — which specific currency a given amount is denominated in, and the fact that two amounts in different currencies should never be added together without an explicit conversion step. Dedicated money-handling libraries typically wrap an integer or arbitrary-precision decimal value together with a currency code, and make arithmetic between mismatched currencies a compile-time or runtime error rather than a silently wrong number, closing a second, related class of bug that pure integer-cents handling alone does not address on its own.
Why a running balance is the specific place drift compounds worst
A single floating-point money calculation might drift by a fraction of a cent, which sounds harmless in isolation, but a running balance updated by thousands of small transactions over time accumulates that tiny per-operation error repeatedly, and the compounded result can eventually diverge from the true balance by an amount large enough to be noticed and disputed — this is precisely the scenario where the integer-cents or decimal-type fix discussed earlier stops being a theoretical best practice and becomes the difference between a ledger that reconciles correctly and one that quietly, slowly drifts wrong.
Why front-end display code is the one place a float is usually harmless
It is worth being precise about scope: a price briefly held as a floating-point number purely to format it for display, with no further arithmetic performed on that specific value afterward, is not the dangerous case this cluster of articles warns against — the risk is specifically in storage and in chains of arithmetic, not in the final, one-time formatting step, and recognizing that distinction prevents overcorrecting into unnecessary complexity in code paths where the actual risk was never present in the first place.
Why interest and tax calculations are where drift becomes a compliance issue
Interest accrual and tax computation frequently apply a percentage rate across a large number of accounts or line items on a recurring schedule, and the same small per-calculation floating-point error that is merely embarrassing in a one-off sum becomes a genuine regulatory and audit problem when it recurs identically across thousands of accounts every billing cycle — jurisdictions that regulate financial reporting often specify exact rounding rules for precisely this reason, and using floating-point arithmetic that cannot guarantee those exact rules are followed consistently is a compliance risk, not merely a rounding inconvenience.
Why splitting a total evenly is the case that forces an explicit policy
Splitting $10.00 three ways in integer cents produces 333, 333, and 334 cents rather than three equal shares, since 1000 does not divide evenly by three — a small but real discrepancy that has to be resolved by an explicit, documented policy (which recipient gets the extra cent, and is it always the same one or does it rotate) rather than left to whatever an arithmetic library happens to do by default, since two different implementations of the same split can legitimately disagree about which recipient absorbs the remainder.
Why exchange rate conversion needs even more care than a single currency's rounding
Converting between currencies multiplies by a rate that is itself rarely a clean, exact number, compounding the rounding-policy question discussed earlier with an additional, independent source of imprecision — the order of operations matters here in a very concrete way: converting then rounding, versus rounding then converting, can produce genuinely different final amounts for the same nominal transaction, which is exactly why financial systems handling multiple currencies specify not just a rounding rule but the precise order every conversion and rounding step must happen in.
Why a QA test suite for money code should include known-drift test cases explicitly
A test suite for financial calculations benefits from deliberately including specific input values already known to be historically problematic for floating-point arithmetic — values like 0.1, 0.2, and their common sums — as explicit regression tests, rather than relying purely on randomly generated test inputs that might never happen to trigger the exact drift pattern this whole subject describes; a team that has been burned by this class of bug once tends to keep exactly such a list on hand afterward.
Why a refund or partial cancellation is another common drift trigger
Refunding part of a transaction — say, one item out of a multi-item order — requires recomputing a proportional share of taxes, discounts, and fees that were originally calculated across the whole order, and doing that recomputation with floating-point arithmetic can produce a refund amount that does not reconcile precisely against the original charge, which is exactly the kind of discrepancy a support agent or an automated reconciliation system will eventually flag, long after the original transaction and its context have been forgotten.
Why this whole subject is really about matching the tool to the guarantee actually needed
Everything covered across this pair of articles reduces to one underlying principle: floating point offers speed and a wide dynamic range at the cost of exactness, while integer and decimal representations offer exactness at some cost in range or convenience, and choosing between them is a matter of knowing which guarantee a specific calculation actually needs rather than defaulting to whichever type a language happens to make easiest to reach for first.
Why a code review checklist for financial code should ask about the numeric type explicitly
Given how easy it is to introduce a plain floating-point variable for a monetary amount without anyone noticing during review, teams handling money seriously often add an explicit line to their review checklist asking specifically what numeric type represents any new monetary value — a small, mechanical check that catches the mistake at the exact point it is cheapest to fix, before it has propagated through calculations, storage, and reports.
Why a data migration is a common, overlooked point where this bug gets introduced
A migration script moving monetary values from one system to another, or from one database column type to another, is a moment where a careless cast to a floating-point intermediate type can silently introduce exactly the imprecision the source and destination systems were both otherwise careful to avoid — auditing exactly this kind of intermediate step during any migration touching monetary data is worth doing explicitly rather than assuming type safety at both ends guarantees safety throughout the whole pipeline.