Why You Shouldn't Use Floating Point Math for Money in JavaScript
Published
4 July 2026
Type 0.1 + 0.2 in JavaScript and you probably expect 0.3. Press Run on the examples below to see what you actually get.
console.log(0.1 + 0.2);That result is not a bug in your bundler, your framework, or your laptop. It is how JavaScript stores numbers. And if you are adding prices, tax, discounts, or ledger entries with plain number values, this behaviour can turn a simple checkout into a reconciliation headache.
The problem in one line
JavaScript has a single number type: IEEE 754 double-precision floating point. It is fast, it is everywhere, and it is excellent for science, graphics, and most everyday arithmetic. It is a poor default for money.
console.log(0.1 + 0.2 === 0.3);The language is doing exactly what the spec says. The surprise is ours, because decimal notation hides what the machine is actually storing.
What causes it
Computers store numbers in binary. Many decimal fractions, including 0.1 and 0.2, cannot be represented exactly in a finite number of binary digits, the same way 1/3 cannot be written exactly in decimal.
So JavaScript stores the closest representable value instead. When you add those approximations together, the tiny errors can show up at the least significant digit. With 0.1 + 0.2, that drift surfaces as 0.30000000000000004.
This is not unique to JavaScript. Python, Java, C#, Ruby, and most languages that use standard floating point behave the same way. The difference is that JavaScript gives you only this number type for everyday maths, so you feel the issue sooner.
Where it hurts in real code
The classic REPL example looks harmless. In production, the same mechanism causes problems that are harder to spot.
Totals that look wrong
Sum a few line items and compare the result to a clean decimal:
const items = [0.1, 0.2];const total = items.reduce((a, b) => a + b, 0);
console.log(total);console.log(total === 0.3);Your UI might show $0.30 because formatting rounds for display, while the underlying value is still 0.30000000000000004. Any === check, threshold rule, or "is this cart fully paid?" guard can fail even when the customer sees the right price.
Tax, discounts, and reconciliation
Money rarely stops at addition. Tax rates, percentage discounts, and split payments multiply the chances of drift:
const price = 19.99;const tax = price * 0.075;
console.log(tax);console.log(price + tax);console.log((price + tax).toFixed(2));Finance teams expect totals that tie out to the cent. Floating point totals often tie out to "close enough" until someone exports to CSV, compares against a payment provider, or runs an audit. That is when $21.48925 versus $21.49 becomes a support ticket.
Accumulated error over many operations
One bad addition is easy to dismiss. Hundreds of them in a ledger, payroll run, or trading simulation are not. Each operation keeps the representation error and can compound. You might never see 0.30000000000000004 in the UI, but you can still store values that are a fraction of a cent off, which matters when balances must match exactly.
False confidence from toFixed
Calling toFixed(2) does not fix the underlying number. It returns a string for display. If you convert that string back to a number, or keep doing maths on the original float, the drift is still there. Display rounding and accounting correctness are different jobs.
What to do instead
You do not need to abandon JavaScript numbers entirely. You need to stop treating them as exact decimals.
Round at the money boundary
If you must stay with floats for small apps or quick scripts, round at the precision you actually care about, usually cents:
const toCents = (value) => Math.round(value * 100) / 100;
console.log(toCents(0.1 + 0.2));console.log(toCents(0.1 + 0.2) === 0.3);That pattern is fine for simple cases. Be consistent about when you round: after each line item, or only on the final total? Tax law and your finance team may care. Document the rule and apply it everywhere.
Store integer cents
The approach I reach for in anything handling real money is to store amounts as integers in the smallest currency unit:
const items = [10, 20]; // centsconst total = items.reduce((sum, amount) => sum + amount, 0);
console.log(total);console.log(`$${(total / 100).toFixed(2)}`);console.log(total === 30);Add cents with integer addition. Multiply with care when percentages are involved, often by doing the percentage maths in integers too, or by using a dedicated decimal library for those steps. Format to dollars only when rendering.
Reach for a decimal library when the domain demands it
For multi-currency ledgers, tax engines, or billing systems with complex rounding rules, libraries such as decimal.js or big.js model decimal arithmetic explicitly. They trade a little performance for predictable results. Many databases also expose decimal/numeric types; keep money in those columns, not float columns.
A practical rule of thumb
Use JavaScript floats for counts, ratios, graphics, and analytics where tiny error is acceptable. Do not use them as the source of truth for money.
If you remember one thing from the 0.1 + 0.2 meme, make it this: the number looks decimal, but the maths is binary. Plan for that at the boundary where money enters or leaves your system, and you will avoid an entire class of bugs that only show up after launch, often first seen in a finance Slack channel on a Friday afternoon.
Similar articles

Production Agentic AI: Permissions, Observability, and Kill Switches
Putting an agentic AI system into production is a different engineering problem to putting it into staging. Once it is live, the governance question shifts from "will it behave?" to "how do we know if it stops behaving, and what do we do about it?" A practical guide to production permissions, observability architecture, and kill switches for Australian engineering leaders in regulated industries.
25 Sept 2026

What Australian CTOs Should Put in the AI Governance Pack
The board is asking. The regulator is watching. The CTO is the one who has to translate "AI governance" from a policy concept into a document set that actually governs something. Here is what a credible AI governance pack looks like for an Australian organisation in 2026, and what each section needs to do to survive scrutiny.
21 Sept 2026

Governing Agentic AI: Human-in-the-Loop Patterns When Tools Can Act (MCP Edition)
Agentic AI doesn't just recommend. It acts. That changes governance from a policy question into an engineering question: which patterns actually limit blast radius, satisfy a human reviewer, and survive scrutiny from a regulator or court? A practical playbook for MCP-connected agents, with a checklist for regulated industries.
16 Sept 2026

Vercel Can Do WebSockets Now. Here's Where a Chat Room Still Breaks It.
Vercel shipped native WebSocket support in 2026, so a Function really can hold a socket open. But the connection is still a function invocation with a duration ceiling and no fan-out. Here's where that's fine for a chat app, where it isn't, and how Supabase Realtime covers the gap.
15 Aug 2026
