Why mandate decimal or fixed-point money in a billing ledger when floats hold 15 digits?
answer
- precision is not the same as exactness
- try writing one hundredth in base two
- millions of postings, each one rounded
- the errors are systematic, not random
- a ledger must reconcile and replay identically
basics
~20 sOne hundredth is non-terminating in binary, so every amount held in a 64-bit float is approximate from the first assignment and error accumulates across millions of postings. Decimal or integer minor units make amounts exact and rounding an explicit, auditable rule.
solid answer
~50 s"Fifteen digits is plenty" answers the wrong question: the issue is representability, not precision. One hundredth has no finite binary expansion, so a cent is approximate however many bits you spend, and a ledger performs millions of additions whose errors are systematic rather than random. What a ledger owes is not "close" but **reconciles and replays identically** — the same postings summed in a different order, on different hardware, must give the same total to the cent. Binary floats cannot promise that; integer minor units or a decimal representation can. The rest of the argument is organisational: a mandate people must remember gets quietly bypassed, so ship one shared money representation with the rounding policy built in, make it the only type crossing a ledger boundary, and enforce that at review. Forecasts and risk models may stay on binary floats, provided their outputs never post back.
go deeper
Know the rule of thumb and its reason: money is not stored in binary floating point, because one hundredth has no exact binary form and so amounts are approximate from the first assignment.
Explain accumulation: each posting rounds and a ledger performs millions of them, so the drift is systematic rather than self-cancelling. Describe integer minor units and decimal representation as the two exact alternatives.
Show that you would make rounding an explicit, tested policy applied at named points, and add a reconciliation job proving the ledger sums to the cent under replay and under a different summation order.
Own the boundary and the cost: decide which subsystems are ledger and which are estimate, ship one money representation instead of a rule people must remember, and plan a migration that reconciles historical data without pausing writes.
## The skeptic is right about the number and wrong about the question A 64-bit float really does carry roughly 15 to 17 significant decimal digits, which comfortably exceeds any single ledger line. But precision is *how many digits you get*, and the problem is *which values exist at all*. Binary floating point represents `(53-bit integer) x 2^k`. One hundredth is not of that form — in binary, 0.01 repeats forever, exactly as one third repeats in decimal — so the very first assignment of an amount is already a neighbour of the value you meant. No amount of extra precision changes that; a wider format simply puts the discrepancy a few digits further out. ## Why accumulation makes it worse, not average out A common defence is that rounding errors are random and cancel. They are not random. Each operation rounds to the *nearest* representable value, and when the same denominators recur — cents, percentages, tax rates — the rounding lands the same direction repeatedly. Across a ledger of millions of postings, the drift is systematic and grows. Worse, it is order-dependent, because floating-point addition is not associative: summing the same day's postings in a different order can produce a total a cent away. A ledger whose total depends on the order rows came back from a query is not a ledger. ## What a ledger actually requires Three properties, none of which are "accurate to 15 digits": 1. **Exactness of representable amounts.** Every value the business can express — every cent — must have an exact representation, so storing and reading it is lossless. 2. **Determinism.** The same inputs must produce the same output on replay, regardless of summation order, hardware or process count. This is what reconciliation and audit rest on. 3. **Explicit rounding.** Division is unavoidable — interest, tax, proration, splitting a charge across line items — and division does not stay exact in any representation. The requirement is that rounding happens at *named* points under a *stated* rule (half-up, or half-to-even to avoid systematic upward bias), and that the residual cent is allocated deliberately rather than lost. Note carefully: an exact representation does not remove the need for a rounding policy; it removes the *drift*, so that the only inexactness left is the one you chose and wrote down. ## The two exact representations **Integer minor units** store the amount as a whole number of the smallest currency unit. Arithmetic is exact integer arithmetic, ordering and equality are trivially correct, and the only care needed is currency-specific scale (not every currency has two decimal places) and the range of the integer type. **Decimal representation** stores a significand with a base-10 exponent, so 0.01 is exact by construction and the scale travels with the value. It handles multiple currencies and high-scale intermediates more gracefully, at the cost of slower arithmetic and a heavier value. Either satisfies the three properties. Which you choose is a local call; that one of them is used is the mandate. ## The organisational half, which is where a mandate actually fails Decimal arithmetic is measurably slower and more verbose than a binary float. A rule expressed as "do not use floats for money" therefore invites quiet workarounds in hot paths, in ad-hoc reports, and in the analytics job that sums the ledger for a dashboard and then feeds a credit back in. Three things make it stick: - **One shared money representation** with the rounding policy baked into its operations, so correctness is the default rather than a discipline. - **A declared boundary.** Name which subsystems are ledger — anything posted, reconciled, invoiced or reported to a regulator — and which are estimate: risk models, forecasts, projections, dashboards, optimisation. Estimates may use binary floats and benefit from their speed and dynamic range, on the condition that their outputs never post back into the ledger. - **Enforcement at the edges,** at code review and at the serialization boundary, rather than by asking people to remember. Measure the hot paths where decimal cost is alleged to matter instead of granting exemptions on assertion; it is usually smaller than claimed, and where it is not, the fix is batching, not floats. ## Migrating without pausing writes Add the exact column alongside the float one, write both through a single money abstraction, and backfill in batches while a reconciliation job continuously compares the two representations and reports divergence. When divergence is zero across a full accounting period, flip reads, then drop the old column. The schema work is the easy part; the expensive part is deciding what the historical float values *should have been*, which needs a documented rule agreed with finance before a single row is backfilled. ## Ecosystems disagree on how much help you get Some mainstream ecosystems ship an exact base-10 numeric type in the standard library and some do not, leaving teams to a third-party library or an integer-minor-unit convention. That difference shapes how the mandate is written — where the type is standard the rule can name it, and where it is not the rule has to name a house library — but it does not change the argument, because every one of them shares the same binary floating-point arithmetic underneath.
- The skeptic says a final round to two decimals hides any drift. What is the counter?Rounding at the end hides drift smaller than half a cent, and hides it non-deterministically: whether a total lands just below or just above the halfway point depends on summation order, so two runs over identical data can differ by a cent. The ledger's requirement is not 'close enough' but 'reconciles and replays identically'. Late rounding also destroys the audit trail, because you can no longer say which posting the disputed cent came from.
- Where does a binary float remain the right choice inside a financial system?Anywhere the output is an estimate rather than a booked amount: risk models, forecasts, interest projections, analytics dashboards, optimisation. Those want speed and wide dynamic range and carry no reconciliation duty. Draw the line at the ledger boundary — values that are posted, reconciled or reported to a regulator are exact; values that merely inform a decision may be binary floats, on the strict condition that they are never summed back into the ledger.
- How do you migrate an existing float-typed amount column without pausing writes?Add the exact column alongside, write both through one money abstraction, and backfill in batches while a reconciliation job compares the two representations and reports divergence. Once divergence stays zero across a full accounting period, flip reads and then drop the old column. The hard part is not the schema change but deciding what the historical float values should have been, which needs a rule documented and agreed with finance before any backfill begins.
- What does the mandate cost, and how do you stop it being quietly ignored?Exact arithmetic is slower and more verbose, so a blanket prohibition invites workarounds in hot paths and in ad-hoc reporting jobs. Ship one shared money representation with the rounding policy built into its operations, make it the only type allowed across a ledger boundary, and enforce that at review and at the serialization edge rather than by memory. Where cost is alleged, measure it; the answer is usually batching rather than an exemption.
A ledger is a measuring cup marked in cents. Binary floats give you a cup whose marks fall just beside the cents, so every pour is slightly off and the misses accumulate jug after jug.
saying these in an interview costs you the question
- Confuses fifteen digits of precision with exact representation
- Says a final rounding step makes any drift disappear
- Stores amounts as floats and rounds only at display
- Assumes an exact type removes the need for a rounding policy
- Treats the migration as a schema change with no reconciliation