How it works
The explanation, with examples you can run.
What a double holds
A 64-bit double has 1 sign bit, 11 exponent bits and 52 fraction bits (plus an implicit leading 1, so 53 significant bits). The value is ±1.fraction × 2^exponent. A 32-bit float has 24 significant bits, about 7 decimal digits.
Fractions that are sums of powers of two (0.5, 0.25, 0.375) are exact. But 0.1 is 1/10, and 10 has a factor of 5, so in binary it repeats forever: 0.0001100110011… The double nearest to it is slightly more than 0.1:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Node printed 0.30000000000000004, false, 0.10000000000000000555, true, 0.9999999999999999, then 9007199254740992 9007199254740992 (above 2⁵³ the gap between doubles is 2, so the +1 is lost), Infinity -Infinity NaN false, and false true.
Special values
IEEE 754 defines more than numbers. Dividing a non-zero number by zero gives ±Infinity rather than an error. Invalid operations such as 0/0 or Math.sqrt(-1) give NaN (not a number), which is unequal to everything including itself, and which spreads through every calculation it touches. There are two zeros, +0 and −0, which compare equal but behave differently (1/−0 is −Infinity). Very small numbers near zero lose precision gradually ("subnormals") instead of dropping straight to zero.
Living with rounding
- Don't test for exact equality of computed results. Check whether the difference is small relative to the magnitude, for example
Math.abs(a - b) <= 1e-9 * Math.max(Math.abs(a), Math.abs(b)).Number.EPSILON(2⁻⁵², the gap between 1 and the next double) is only a sensible tolerance for values near 1. - Order matters. Floating-point addition isn't associative:
(a + b) + ccan differ froma + (b + c). Summing many values of mixed size is more accurate smallest first, or with compensated (Kahan) summation. Parallel sums can give slightly different answers from run to run because the order changes. - Use integers for counting. Loop on an integer and compute
i * 0.1, rather than adding 0.1 repeatedly. - Money needs exact decimals. Store amounts as integer cents (or the currency's smallest unit), or use a decimal type:
decimalin C#,BigDecimalin Java,Decimalin Python,NUMERICin SQL. Decide rounding rules (for example banker's rounding) explicitly. - Display is not storage.
toFixed(2)and format strings round for display only; the stored value is unchanged.
These rules aren't JavaScript quirks. C, Java, Python, C# and Go give the same answers for the same operations on doubles, because they all use the same hardware format.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.