Every JavaScript Number is a double-precision IEEE 754 binary64 value: 64 bits, of which 52 hold the fraction. Binary can only represent fractions whose denominator is a power of two. 0.1 is 1/10, which in binary is a repeating fraction (like 1/3 in decimal), so it is rounded to the nearest representable double. The same applies to 0.2 and 0.3.
So the literals are not the values you typed:
0.1is stored as about0.10000000000000000550.2is stored as about0.20000000000000001110.3is stored as about0.2999999999999999889
Adding the first two gives about 0.3000000000000000166, which is not representable either. The spec rounds the exact result to the nearest double (ties to even), and that double is 0.30000000000000004. It is one step above the double that 0.3 rounds to, so === is false.
This is not a JavaScript bug. Any language using binary64 does the same. The error is tiny and relative to the magnitude of the number, which gives the practical rules:
- Never compare computed floats with
===. Check that they are close enough:Math.abs(a - b)below a tolerance. Number.EPSILONis the gap between1and the next double (2 ** -52). It is a sensible tolerance only for values near1. For other magnitudes, scale it by the operands.- For money, use integers (cents) or a decimal library, not floats.
- Display with
toFixed/Intl.NumberFormatinstead of trying to "fix" the stored value.
Some sums are exact: 0.5 + 0.25 === 0.75, because those fractions are powers of two.