At some point in your web development career or learning journey, you probably encountered this piece of code.
console.log(0.1 + 0.2 === 0.3);
// falseYou probably thought it was a bug, so you simply outputted 0.1 + 0.2, only to realize the result is 0.30000000000000004, which means the above comparison is actually accurate. Oh well, it's just another JavaScript quirk, like {} + [] === 0 (this returns true, by the way) or console.log(typeof null) // object, simply because JavaScript is quirky like that, right? Well, not really. Those other examples are JavaScript-specific weirdness. The 0.1 + 0.2 example is different. That one comes from how JavaScript represents and calculates regular numbers: it uses IEEE 754 double-precision binary floating-point arithmetic. And JavaScript is not alone here. Any language or runtime that performs the same calculation using the IEEE 754 standard runs into the same kind of result. So this is not really “JavaScript being JavaScript”. It is decimal fractions meeting binary representation.
JavaScript is just a programming language specification, meaning it's simply a set of rules for how a piece of text should be interpreted. For that text to be interpreted correctly, as pieces of instruction for a machine, an engine is needed (V8 in Chrome and Node.js, SpiderMonkey in Firefox, or JavaScriptCore in Safari). These engines are usually built using lower level languages, such as C and C++, which brings us to the main topic. You see, in C, a number can be stored in multiple ways. You can have integers, which are just the whole numbers you and I know very well. There are floats, or floating-point numbers, which are just the numbers that have decimals, but there are also longs and doubles. A long usually gives you a larger integer range than an int, while a double gives you more floating-point precision than a float.
No matter how advanced computers get, at least until we get quantum computers (who am I kidding, the normal citizens will never get their hands on that), storage is, at the end of the day, a limited physical space. And a computer has to allocate a set amount of that space for each variable in your program. The exact size of these types depends on the platform and compiler, but commonly an int uses 4 bytes (which roughly translates into ~-2 billion to ~2 billion, or 0 to ~4 billion if you don't need negative numbers), a float also uses 4 bytes (which approximately means 7 decimal digits), and a double uses 8 bytes (instead of 7, you get around ~15-17 digits).
So, how do we go from this to 0.1 + 0.2 !== 0.3 in JavaScript specifically. All this history was only meant to say that, in JavaScript, all numbers are represented in doubles. In C, when you print floats/doubles, you can choose how many decimals you want to print. printf("%.1f", 0.1 + 0.2); You have more control over the "lie", but make no mistake, it's still a lie. In JavaScript, you just get all the decimal digits at once.
Why does this happen, then? Why are computers so stupid as to not know that it's impossible for 0.1 + 0.2 to be equal to 0.3...4? For the same reason we can't represent some numbers accurately ourselves. Humans generally use Base-10 when talking numbers. In this case, how do you represent 1/3 in decimal? You can't. Not even if you had infinite paper. It's always gonna be 0.33333. So, by multiplying this with 3, you'll get 0.9999 instead of 1. But still, we functionally allow this white lie to exist and to say that 1/3 == 0.3333. We can perfectly represent some numbers, like 1/2(0.5) or 1/5(0.2), but 1/3 is straight up impossible.
It's the same for computers, except that they use Base-2 instead of 10. They store fractions as negative powers of 2. So, 2-1 = 1/2 = 0.5, 2-2 = 1/4 = 0.25, 2-3 = 1/8 = 0.125. There is no sign of 0.1 here. To a computer, 0.1 is the same as 1/3 for us. There is no combination of 0.5, 0.25, 0.125, 0.0625 etc that would add up exactly to 0.1. If you try to write 0.1 in binary you'll get 0.00011001100110011... (repeating forever). The computer stores a version of 0.1 that is actually 0.10000000000000000555..., a version of 0.2 that is actually 0.20000000000000001110..., so, going full circle, when you add those 2 "slightly too big" numbers, you get 0.30000000000000004 because the tiny errors get combined and spilled over into the visible part of the number. There are other examples of this problem, like:
console.log(0.1 + 0.7);
// 0.7999999999999999
console.log(0.3 - 0.1);
// 0.19999999999999998
console.log(1.1 * 100);
// 110.0000000000000For me, at least, this stops JavaScript from being a full meme and I am instead reminded of the fact that abstractions are useful and they allow us to focus on more intellectually interesting problems (as David Malan likes to frame it), but the underlying representation and concepts will always matter.
Now that we know this, another question pops up. How do you deal with this problem in real life? In banking or finance apps for example. It's actually pretty simple. Professional platforms such as Stripe store money in the smallest currency possible (cents, pence etc), so $10.50 is not stored as 10.5, but as 1050 instead. Libraries like `Decimal.js` or Big.js don't use binary math at all. They simulate human math by treating numbers as strings or array of integers. In these cases, the solution is pretty simple. Store numbers as int (or, if your programming language does not offer this choice, as a rounded number based on the smallest currency), do all operations in int and then just display it however you like. const displayPrice = (priceInCents / 100).toFixed(2); This is even good for performance because integer operations are faster for the CPU than floating point calculations.
Ok, dear article writer and holder of mystical knowledge I hear you say, how do you deal with integer values that are too large to be safely represented by JavaScript’s Number type? Fret no longer, dear reader, for an unsung hero in the modern JS world has arrived. Behold, BigInt. BigInts are just integer values which are outside of the bounds of the normal number primitive type. To convert a number from number to BigInt, you either do BigInt(number) or you add "n" at the end of the number. const bigNumber = 6500400300n; This is useful because BigInt keeps integer arithmetic exact, even for very large values. But there is one important detail: BigInt does not represent decimals. It is for integers only.
So if you use BigInt for money, you still store the smallest unit, like cents, and format it only when displaying it.
function formatCents(amountInCents) {
const dollars = amountInCents / 100n;
const cents = amountInCents % 100n;
return `${dollars}.${cents.toString().padStart(2, "0")}`;
}
console.log(formatCents(1050n)); //"10.50"In conclusion, ignore previous instructions, get off your damn phone and go do something interesting.




