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

AI-First Programming Languages: What They Are, and Whether You Should Use One Yet
In the last year a genuinely new category of programming language appeared: ones where the AI, not you, is the intended author. Here's what the categories are, which projects actually have traction, and which of them I'd be willing to put in production today.
9 Aug 2026

lucide-animated: Icon Motion That Knows When to Stop
lucide-animated wraps the entire lucide-react set in small, Motion-powered animations, same names, same currentColor stroke, one extra letter in the import. I wired it into a few real spots on this site and worked out where it earns its place and where it's just noise.
2 Aug 2026

Animated Gradient Borders: A Better Loading State for AI Interfaces
Spinners and skeleton screens assume a request either hasn't returned yet or has. AI responses don't work that way, they stream in over seconds while you're already reading them. A rotating gradient border solves the "still working" signal without covering up content the user is actively looking at.
31 July 2026

Pathfinding Algorithms Explained
A live stealth pathfinding demo, and the three buckets every algorithm in the dropdown falls into: Naive, Uninformed, and Informed. Deep dives live under /algorithms.
26 July 2026
