Why 0.1 + 0.2 !== 0.3 (And What To Do About It)

JS Edition

March 20, 2026

Type this into your browser console:

0.1 + 0.2

You get: 0.30000000000000004

Not 0.3. Not even close. Off by 0.00000000000000004.

This isn’t a JavaScript bug. This is how computers work. And it breaks shopping carts, financial calculations, and junior developers’ brains on a regular basis.

Let me explain why your computer can’t do elementary school math.

The Problem is Universal

Before we blame JavaScript, let’s be clear: every programming language has this problem.

Python:

0.1 + 0.2  # 0.30000000000000004

Java:

0.1 + 0.2  // 0.30000000000000004

C:

0.1 + 0.2  // 0.30000000000000004

Ruby:

0.1 + 0.2  # 0.30000000000000004

It’s not the language. It’s IEEE 754, the floating-point standard that all modern computers use.

And yes, it’s been like this since 1985. We’ve just accepted that computers can’t add.

Why Computers Are Bad At Decimals

Computers store numbers in binary (base 2). You know: 0s and 1s.

Decimal 0.1 in binary is:

0.0001100110011001100110011001100110011001100110011001...

It repeats forever. Like how 1/3 = 0.333333... in decimal.

But floating-point numbers have limited space. JavaScript uses 64 bits (IEEE 754 double precision). So the computer has to round.

0.1 → 0.1000000000000000055511151231257827021181583404541015625\n0.2 → 0.2000000000000000111022302462515654042363166809082031250

These aren’t exactly 0.1 and 0.2. They’re the closest representable values.

Add them together:

0.30000000000000004440892098500626161694526672363281250

Which displays as: 0.30000000000000004

Your computer didn’t mess up. It just can’t store 0.1 exactly.

The Practical Disasters

This isn’t academic. Real code breaks because of this:

Shopping cart math:

let total = 0;\nitems.forEach(item => {\n  total += item.price;  // 0.1, 0.2, 0.3...\n});\n\n// total is now 0.6000000000000001 instead of 0.6\n// Display to user: "$0.60" (after rounding)\n// Send to payment processor: 0.6000000000000001\n// Payment processor: "Invalid amount"

Comparison hell:

const price = 0.1 + 0.2;\n\nif (price === 0.3) {\n  applyDiscount();  // Never runs\n}\n\n// Because:\nconsole.log(price === 0.3);  // false

Accumulation errors:

let balance = 0;\nfor (let i = 0; i < 1000; i++) {\n  balance += 0.1;\n}\n\nconsole.log(balance);  // 100.00000000000001\n// Should be exactly 100

After 1000 additions, you’re off by one hundredth of a cent. In a financial system with billions of transactions? That adds up to real money.

Why It’s Worse Than You Think

Multiplication makes it weirder:

0.1 * 0.2  // 0.020000000000000004

Division is a minefield:

0.3 / 0.1  // 2.9999999999999996 (not 3!)

Even integers break eventually:

9007199254740992 + 1  // 9007199254740992 (didn't add!)

Once you pass Number.MAX_SAFE_INTEGER (2^53 - 1), integers lose precision too.

This one broke Twitter:

const tweetId = 9007199254740993;  // Tweet ID from API\nconsole.log(tweetId);  // 9007199254740992 (different number!)

Twitter IDs went past the safe integer limit. JavaScript couldn’t store them accurately. Tweets disappeared. The API started returning IDs as strings.

The “Just Use toFixed” Solution (That Doesn’t Work)

Everyone’s first solution:

(0.1 + 0.2).toFixed(2)  // "0.30"

Great! Problem solved, right?

Wrong.

const result = (0.1 + 0.2).toFixed(2);  // "0.30"\ntypeof result  // "string"\n\nresult === 0.3  // false (string vs number)\nresult == 0.3   // true (coerces string to number)\n\n// And then:\nresult + 0.1  // "0.300.1" (string concatenation!)

toFixed() returns a string. Now you have string vs number problems on top of floating point problems.

Also:

1.005.toFixed(2)  // "1.00" (should be "1.01")

toFixed() uses “round half to even” (banker’s rounding), which is correct mathematically but surprising in practice.

The Epsilon Comparison (The Real Solution)

Instead of checking if two numbers are equal, check if they’re “close enough”:

function almostEqual(a, b) {\n  return Math.abs(a - b) < Number.EPSILON;\n}\n\nalmostEqual(0.1 + 0.2, 0.3)  // true

Number.EPSILON is the smallest difference between two representable numbers: 2.220446049250313e-16

If two numbers differ by less than that, they’re “equal enough” for most purposes.

Better version with configurable tolerance:

function almostEqual(a, b, epsilon = Number.EPSILON) {\n  return Math.abs(a - b) < epsilon;\n}\n\n// For money (2 decimal places), use larger epsilon:\nalmostEqual(0.1 + 0.2, 0.3, 0.01)  // true

This is what game engines, physics simulations, and graphics libraries do.

The Integer Solution (For Money)

Store money as cents, not dollars.

// Bad:\nlet price = 19.99;\nlet tax = price * 0.08;  // 1.5992000000000001\nlet total = price + tax;  // 21.589200000000002\n\n// Good:\nlet priceInCents = 1999;  // 19.99 * 100\nlet taxInCents = Math.round(priceInCents * 0.08);  // 160\nlet totalInCents = priceInCents + taxInCents;  // 2159\n\n// Display:\nconst total = totalInCents / 100;  // 21.59

Integers are exact up to Number.MAX_SAFE_INTEGER. No rounding errors.

Stripe does this. Their API uses cents:

stripe.charges.create({\n  amount: 2000,  // $20.00 in cents\n  currency: 'usd'\n});

PayPal does this. Their API uses cents.

Your shopping cart should do this.

The Decimal Library Solution

For complex financial calculations, use a library:

decimal.js:

import Decimal from 'decimal.js';\n\nconst a = new Decimal(0.1);\nconst b = new Decimal(0.2);\nconst result = a.plus(b);  // Decimal object\n\nresult.toString()  // "0.3" (exact!)\nresult.toNumber()  // 0.3

big.js:

import Big from 'big.js';\n\nconst a = new Big(0.1);\nconst b = new Big(0.2);\nconst result = a.plus(b);  // Big object\n\nresult.toString()  // "0.3"

bignumber.js:

import BigNumber from 'bignumber.js';\n\nconst a = new BigNumber(0.1);\nconst b = new BigNumber(0.2);\nconst result = a.plus(b);\n\nresult.toString()  // "0.3"

These libraries use string-based arithmetic. Slower than native numbers, but exact.

The BigInt Solution (For Integers Only)

JavaScript added BigInt for arbitrary-precision integers:

const big = 9007199254740993n;  // Note the 'n'\nconst bigger = big + 1n;  // 9007199254740994n\n\n// Twitter IDs as BigInt:\nconst tweetId = 9007199254740993n;\nconsole.log(tweetId);  // 9007199254740993n (exact!)

But BigInt doesn’t help with decimals:

const a = 0.1n;  // Error: Cannot convert 0.1 to a BigInt

BigInt is for integers only. Use it for:

  • Database IDs

  • Timestamps

  • Cryptocurrency amounts (stored as smallest unit)

  • Large integers from APIs

Don’t use it for money with decimal places.

When Floating Point is Fine

Not everything needs precision:

Game development:

player.x += velocity * deltaTime;\n// Close enough for 60 FPS

Data visualization:

const average = total / count;\n// Chart doesn't need perfect precision

UI animations:

element.style.left = position + 'px';\n// Pixels are integers anyway

Scientific calculations:

const distance = Math.sqrt(dx*dx + dy*dy);\n// Measurements have error anyway

Floating point is fast. Native CPU operations. If you don’t need exact decimal precision, use it.

The Real Rules

Use floating point when:

  • Doing graphics/game math

  • Scientific calculations

  • Performance matters

  • Exact decimal precision doesn’t

Use integers (cents) when:

  • Dealing with money

  • You need exactly 2 decimal places

  • Simple arithmetic only

Use decimal libraries when:

  • Complex financial calculations

  • Need arbitrary precision

  • Can’t use integer math

  • Correctness > performance

Use BigInt when:

  • Need integers beyond 2^53

  • Database IDs

  • Timestamps in nanoseconds

  • Cryptocurrency (store as satoshis/wei)

The Mental Model

Think of floating point like measuring with a ruler:

You can’t measure 0.1 inches exactly. You get “about 0.1 inches.” Close enough for carpentry, but not for watchmaking.

Computers are the same. 0.1 in binary is like 1/3 in decimal - it repeats forever. The computer picks “close enough.”

For most tasks, close enough is fine. For money, it’s not fine. That’s when you switch to precise tools.

The Comparison That Actually Works

Never do this:

if (price === 0.3) {\n  // This will fail\n}

Do this:

// For general floating point:\nif (Math.abs(price - 0.3) < Number.EPSILON) {\n  // Probably works\n}\n\n// For money (2 decimal places):\nif (Math.round(price * 100) === Math.round(0.3 * 100)) {\n  // This works\n}\n\n// Or just use cents:\nif (priceInCents === 30) {\n  // Best option\n}

Why We Can’t Fix This

“Why doesn’t JavaScript just use exact decimals?”

Because it would be slow. IEEE 754 floating point uses hardware. It’s fast because your CPU has dedicated floating-point units.

Exact decimal arithmetic requires software. It’s 10-100x slower.

Languages with built-in decimal types (Python’s Decimal, Java’s BigDecimal) are slow for math. That’s why they also have float/double for fast approximate math.

JavaScript chose speed by default. Want precision? Use a library.

The Classic Fails

These will never not be funny:

// The trilogy:\n0.1 + 0.2 === 0.3           // false\n0.1 + 0.2 === 0.30000000000000004  // true\nMath.floor(0.1 + 0.2)       // 0 (not 1!)\n\n// The sequel:\n0.3 - 0.2 === 0.1           // false\n0.3 - 0.1 === 0.2           // true (sometimes it works!)\n\n// The twist:\n9.7 + 1.3                   // 11 (this one works)\n9.7 + 1.3 === 11            // true (wait, what?)\n\n// The finale:\n0.1 * 0.1                   // 0.010000000000000002\nMath.sqrt(0.01)             // 0.09999999999999999 (not 0.1!)

Floating point is chaos. Embrace it.

How Other Languages Handle This

Python:

from decimal import Decimal\n\nDecimal('0.1') + Decimal('0.2')  # Decimal('0.3')

Built-in, but you have to opt in.

Rust:

// No built-in decimal, use rust_decimal crate\nuse rust_decimal::Decimal;\n\nlet a = Decimal::new(1, 1);  // 0.1\nlet b = Decimal::new(2, 1);  // 0.2\nlet c = a + b;  // 0.3 (exact)

Java:

import java.math.BigDecimal;\n\nBigDecimal a = new BigDecimal("0.1");\nBigDecimal b = new BigDecimal("0.2");\nBigDecimal c = a.add(b);  // 0.3

Most languages have decimal libraries. Few make them the default because performance.

My Honest Take

I’ve been burned by floating point math exactly once in production. Shopping cart totals didn’t match payment processor amounts. Took three hours to debug.

The fix? Store everything in cents. Problem disappeared.

Most developers will never hit this bug because:

  1. They don’t do financial calculations

  2. They round for display anyway

  3. Small errors don’t matter in their domain

But when you DO hit it, it’s confusing and infuriating because your elementary school math teacher lied to you. Computers can’t do basic arithmetic.

The solution is simple: don’t use floating point for money. Use integers (cents). If you need arbitrary precision, use a decimal library.

For everything else? Floating point is fine. Your game’s physics don’t need perfect precision. Your chart doesn’t care about 0.00000000000000004.

Just remember: 0.1 + 0.2 will never equal 0.3 in JavaScript. Or Python. Or any language using IEEE 754.

And that’s okay.


Try it yourself:

// The floating point hall of shame:\nconsole.log(0.1 + 0.2);  // 0.30000000000000004\nconsole.log(0.3 - 0.2);  // 0.09999999999999998\nconsole.log(0.2 + 0.4);  // 0.6000000000000001\nconsole.log(0.3 / 0.1);  // 2.9999999999999996\n\n// The integer solution:\nconst a = 10;  // 0.1 in cents (actually 0.10)\nconst b = 20;  // 0.2 in cents (actually 0.20)\nconsole.log(a + b);  // 30 (exactly!)\nconsole.log((a + b) / 100);  // 0.3 (perfect)\n\n// You're welcome.

Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.

Thanks for reading Under The Hood! This post is public so feel free to share it.

Share

Leave a comment