TWISTEdBRACKETS

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.

addition.js
console.log(0.1 + 0.2);
ConsolePress “Run” to execute this script.
$ node ./addition.js

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.

equality.js
console.log(0.1 + 0.2 === 0.3);
ConsolePress “Run” to execute this script.
$ node ./equality.js

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:

checkout.js
const items = [0.1, 0.2];const total = items.reduce((a, b) => a + b, 0);
console.log(total);console.log(total === 0.3);
ConsolePress “Run” to execute this script.
$ node ./checkout.js

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:

tax.js
const price = 19.99;const tax = price * 0.075;
console.log(tax);console.log(price + tax);console.log((price + tax).toFixed(2));
ConsolePress “Run” to execute this script.
$ node ./tax.js

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:

round_to_cents.js
const toCents = (value) => Math.round(value * 100) / 100;
console.log(toCents(0.1 + 0.2));console.log(toCents(0.1 + 0.2) === 0.3);
ConsolePress “Run” to execute this script.
$ node ./round_to_cents.js

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:

integer_cents.js
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);
ConsolePress “Run” to execute this script.
$ node ./integer_cents.js

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.