Build1 distinct publisher3 min readUpdated
A dev.to post argues the expensive defect is not float drift but a money type that carries an amount and nothing else. Swapping doubles for Decimal leaves the hole open.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A post on dev.to takes the standard money advice, demonstrated with `0.1 + 0.2 === 0.30000000000000004` and concluded with "don't use floats," and calls it true but not very useful [1]. The defect it says is most likely to reach production is different in kind: a function that adds 500 US dollars to 500 Japanese yen and returns 1000 of nothing [2].
The framing is that money is four things at once, a number, a currency, a scale and a rounding policy, and that a type carrying only the first scatters the other three across call sites as assumptions [3].
There are two float bugs, and only one of them is loud. Adding 0.1 ten times and landing on 0.9999999999999999 announces itself the moment you print it [4]. The quiet one is `Math.round(1.005 * 100) / 100`, which returns 1 rather than 1.01 [5], because the nearest double to 1.005 is 1.00499999999999989342, so multiplying by 100 gives 100.49999999999999 and rounding that down is arithmetically correct [6]. The author's point is operational: round-to-nearest-cent is the single operation nobody thinks to unit test, and it is wrong for a value a human typed into a form [7]. There is also a ceiling, since doubles are exact on integers only up to `Number.MAX_SAFE_INTEGER`, 9007199254740991 [8], roughly 90 trillion dollars in cents, which holds until an amount arrives in a currency with a smaller minor unit or you accumulate a notional across a whole book [9]. At an exponent of 3 that same headroom is about 9 trillion units [10].
decimal.js, Java's `BigDecimal` and Postgres `NUMERIC` all hold 1.005 exactly, and every arithmetic complaint above goes away [11]. The currency complaint does not: `new Decimal("500").plus(new Decimal("500"))` is 1000 of nothing in particular, because a Decimal carries no currency, no scale policy and no rounding mode [12]. In the author's experience, essentially every expensive money bug lives in that hole rather than in the third decimal place [13].
The recommended representation is integer minor units in a `BigInt`: exact, with no rounding at rest [14]. The trap arrives one line later, when someone divides by 100 for display, which the post says is wrong for a third of the world because ISO 4217 assigns every currency an exponent and it is not always 2 [15]. JPY, KRW and VND are zero-decimal; KWD, BHD, OMR, JOD and TND are three-decimal [16]. So the exponent is a lookup, and the decimal string it produces can be handed to `Intl.NumberFormat`, whose `format()` accepts a string rather than only a number, so an exact value never round-trips through a double [17]. Intl already knows each currency's exponent [18].
On rounding, half away from zero sends every tie the same direction, so error accumulates instead of cancelling: over 0.5 to 4.5 the exact sum is 12.5, half-up gives 15 and half-even gives 12 [19]. That is an overshoot of 2.5 against an undershoot of 0.5 on five ties [20]. Half-even is the IEEE-754 default [21] and still not automatically correct, because tax authorities, card scheme rules and loan regulations sometimes specify a mode, and not always the unbiased one [22]. Which is why the mode belongs in the signature, `convert(from, rate, mode)`, rather than defaulted three layers down in a formatting helper [23].
Then there is splitting. Ten dollars three ways gives 3.33 each, three of which is 9.99; a cent has gone missing, and if the other side of the entry is a real bank transfer the ledger no longer balances [24]. The operation you want is allocation, not division [25].
Two things worth checking in your own code, both cheap. Any `/ 100` in a display path is an assumption about exponent that will fail on a yen or dinar amount [15][16]. Any rounding call reachable from a formatter is a policy decision that no signature records [23].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Writing const display = amount / 100 is wrong for a third of the world, because ISO 4217 assigns every currency an exponent and it is not always 2; the exponent must be a lookup, never a literal.
Zero-decimal currencies include JPY, KRW and VND; three-decimal currencies include KWD, BHD, OMR, JOD and TND.
Half-even is not automatically the right answer: tax authorities, card scheme rules and loan regulations sometimes specify a mode, and it is not always the unbiased one.
Division is not the operation wanted for splitting money; allocation is.
The bug most likely to reach production, per the post, is a function that adds 500 US dollars to 500 Japanese yen and returns 1000 of nothing.
Money is a number, a currency, a scale and a rounding policy; if the type carries only the first, the other three end up scattered across call sites as assumptions.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-verifiable technical core, single source
Most claims are independently checkable against language and standards behaviour and are stated with exact values: the nearest double to 1.005, Number.MAX_SAFE_INTEGER, the half-up versus half-even tie table, ISO 4217 zero- and three-decimal currency lists, Intl.NumberFormat accepting a string, and a complete allocate() implementation with worked outputs. That is strong for a how-to argument. It is capped by the cluster having exactly one publisher and by the central motivating assertion — that essentially every expensive money bug lives in the missing-currency hole — being personal experience with no data.
No adoption signal supplied
The source is a design argument with illustrative code. It contains no release, deployment, download, usage-disclosure, benchmark or incident data, and names decimal.js, Java's BigDecimal and Postgres NUMERIC only as representation alternatives without any indication of who has adopted the recommended BigInt-minor-units-plus-explicit-rounding pattern. Nothing in the supplied material can be scored as adoption without inventing facts.
Mildly overstated framing over solid mechanics
The mechanics are conservative and mostly verifiable, and the post explicitly limits itself (half-even is 'not automatically the right answer'; the author discloses their own refund bug), which pulls the gap toward zero. It goes slightly positive on framing: the headline generalization that essentially every expensive money bug lives in the missing-currency hole, plus the unquantified 'most guides' and 'wrong for a third of the world' proportions, assert more than the supplied evidence establishes. No adoption or performance claims are made, so there is no vendor-style overreach to penalise.
Low: individual author, no product on offer
The supplied source is a personal engineering post published on dev.to by an individual author. It sells nothing, links to no product, and names decimal.js, Java's BigDecimal and Postgres NUMERIC as generic alternatives rather than promoting one; the recommended solution is plain-language BigInt and standard-library Intl.NumberFormat, so there is no commercial beneficiary. The residual incentive is reputational — establishing authority via the appeal to the author's own experience and a strong headline framing on a platform where engagement rewards contrarian takes.
Moderate: reproducible facts, one publisher, no adoption
Confidence is held mid-range by structure rather than by content quality. The technical claims are precise and reproducible, and the incentive picture is clean, which supports the assessment of what the post says. Against that, the cluster has a single publisher with no corroboration, adoption is entirely unmeasurable from the supplied material, and the thesis that gives the story its news value is anecdotal.