The sum, with all its digits
Every number on this page is computed by the page itself, in the same 64-bit arithmetic it describes.
| Expression | Result |
|---|---|
| 0.1 + 0.2 | 0.30000000000000004 |
| (0.1 + 0.2).toFixed(20) | 0.30000000000000004441 |
| (0.3).toFixed(20) | 0.29999999999999998890 |
| 0.1 + 0.2 === 0.3 | false |
| 0.1 + 0.1 + 0.1 | 0.30000000000000004 |
The two 20-digit lines are the point. The stored value of 0.3 is a little below 0.3, and the sum of the stored 0.1 and 0.2 is a little above it. They are two different 64-bit values, one step apart, so the comparison is false. The 17-digit printout is simply the shortest string that identifies the stored sum; it is not an error that appeared out of nowhere, it was there in the inputs.
What the 64 bits hold
A JavaScript number is a double-precision 64-bit IEEE 754 value, the same type as a double in Java or C. MDN gives the layout: 1 bit for the sign, 11 bits for the exponent and 52 bits for the fraction, also called the mantissa or significand.
| Field | Bits | What it stores |
|---|---|---|
| Sign | 1 | Positive or negative |
| Exponent | 11 | A power of two from −1022 to 1023, which places the binary point |
| Fraction | 52 | The digits after a leading 1 that is not stored, so 53 binary digits of precision in all |
Fifty-three binary digits are worth about 15 to 17 decimal digits. The gap between 1 and the next representable number is 2−52, available as Number.EPSILON, which is 2.220446049250313e-16. The 64 bits can hold huge values, up to 1.80 × 10308 (Number.MAX_VALUE) before the result becomes Infinity, but never more than 53 bits of precision at any size. The number base converter shows any number in binary, including the fraction part, which is where the trouble with 0.1 starts.
Why 0.1 cannot be stored
A fraction has a finite expansion in a base only if its denominator divides a power of that base. In decimal, 1/4 is 0.25 and 1/8 is 0.125, but 1/3 is 0.333… forever, because 3 does not divide any power of 10. In binary the only finite fractions are the ones with a power of two underneath: 1/2, 1/4, 3/8. One tenth is 1/10, and 10 has the factor 5, so in binary it repeats forever:
0.1 = 0.000110011001100110011001… (the block 0011 repeats)
The 53 digits that fit are kept, the rest is rounded, and the number that is actually stored is, written out in full:
0.1000000000000000055511151231257827021181583404541015625
Python's documentation gives the same value as a fraction, 3602879701896397 / 255, and spells out the rule: most decimal fractions cannot be represented exactly as binary fractions, just as 1/3 cannot in decimal. The table shows which short decimals are exact and what the others really hold.
| You type | Stored value, 20 digits | Exact? |
|---|---|---|
| 0.5 | 0.50000000000000000000 | Yes |
| 0.25 | 0.25000000000000000000 | Yes |
| 0.1 | 0.10000000000000000555 | No |
| 0.2 | 0.20000000000000001110 | No |
| 0.3 | 0.29999999999999998890 | No |
| 0.7 | 0.69999999999999995559 | No |
| 1.1 | 1.10000000000000008882 | No |
| 2.675 | 2.67499999999999982236 | No |
0.5 and 0.25 are exact because they are 1/2 and 1/4. Everything else in the table is a rounded neighbor, some just below the decimal you typed, some just above. The direction matters later, when you round.
Why you usually do not see it
Languages print the shortest decimal that reads back to the same stored value. For 0.1 that string is simply 0.1, so the error stays hidden. It surfaces when an operation moves the result to a different stored value than the one the short string would pick: 0.1 + 0.2 lands one step above the stored 0.3, so the shortest string for it needs 17 digits. Multiplying a price by 100 does the same thing:
| Price | Price × 100 | Math.round(price × 100) |
|---|---|---|
| 19.99 | 1998.9999999999998 | 1999 |
| 4.35 | 434.99999999999994 | 435 |
| 0.07 | 7.000000000000001 | 7 |
| 1.1 | 110.00000000000001 | 110 |
| 1.005 | 100.49999999999999 | 100 |
5 of the 5 products are not whole numbers, and the last row is the dangerous one: the stored 1.005 is already below 1.005, so 1.005 × 100 gives 100.49999999999999 and even Math.round returns 100 cents, not 101. Rounding repairs the first four, where the error came from the multiplication. For the fifth it was in the typed number before any arithmetic, which is why the money rule below says to turn the typed amount into cents directly instead of multiplying a float.
Safe integers
Integers are exact as long as they fit in the 53 bits of precision. MDN states the range: from −(253 − 1) to 253 − 1, which is 9,007,199,254,740,991, available as Number.MAX_SAFE_INTEGER. Safe means the integer is represented exactly and compares correctly. Beyond it, the gaps between representable numbers grow to 2, then 4, then 8, so some integers simply do not exist:
| Expression | Result |
|---|---|
| 2 ** 53 | 9,007,199,254,740,992 |
| 2 ** 53 + 1 | 9,007,199,254,740,992 |
| 2 ** 53 === 2 ** 53 + 1 | true |
This is why 64-bit IDs from databases and APIs must travel as strings in JSON, not as numbers: a 19-digit ID parsed into a JavaScript number is silently rounded to a neighbor and the lookup fails. JavaScript's BigInt type holds integers of any size exactly, at the cost of not mixing with ordinary numbers.
How to compare
MDN's advice is short: floating point numbers should never be compared with ===. Four ways that work, from quickest to most exact.
- A tolerance. Treat two numbers as equal when their difference is smaller than a threshold. Math.abs(a − b) < Number.EPSILON is true for 0.1 + 0.2 against 0.3 (true), but EPSILON only fits values around 1; MDN's example scales it, 2000 × EPSILON for values around 2000. Python's math.isclose() does the scaling for you.
- Round first, then compare. If the numbers mean dollars or percentages, round both to the digits that matter and compare the rounded values. The rounding calculator shows what each rounding rule does to a tie like 2.5 or 2.675.
- Integers. Keep money in whole cents, quantities in the smallest unit, and the comparison becomes exact integer arithmetic up to 253. Convert to dollars only for display.
- A decimal type. Python's decimal module does decimal arithmetic suitable for accounting, and its fractions module stores 1/3 exactly. JavaScript has no built-in equivalent, but several libraries do the same job in base 10.
For sums of many values, Python's documentation also points to math.fsum(), which tracks the lost digits across the whole sum and rounds once at the end.
How rounding tools decide
Rounding a float with the language's own function rounds the stored value, not the number you typed. The stored 1.005 is 1.00499999999999989342, below the halfway point, so (1.005).toFixed(2) returns 1.00. Python's round(2.675, 2) gives 2.67 for the same reason, and its documentation uses that very example. Nothing is wrong with the rounding rule; the input was already a hair short.
Our calculators avoid this by never turning your number into binary. The rounding calculator keeps the digits you typed as text and rounds them with exact whole-number arithmetic, so 1.005 to two places is 1.01 and 2.675 is 2.68, under every tie rule it offers. The significant figures calculator and the scientific notation converter work the same way: the digits are moved and counted as text, so a trailing zero you typed on purpose survives, 1e-500 converts without becoming 0, and a 30-digit number keeps all 30 digits instead of the 15 to 17 a double can hold. If you need to reproduce a program's result rather than the correct decimal one, round the stored value shown in the tables above instead.
Rules of thumb
- Expect any decimal with a 5 in its denominator to be stored slightly off. 0.5, 0.25 and whole numbers are exact.
- Never compare floats with ===. Use a scaled tolerance, round first, or work in integers.
- Money goes in cents, as integers. Parse the typed amount into cents; when you must multiply a float by 100, round the result, never truncate.
- Integers above 9,007,199,254,740,991 need a string or BigInt.
- Round for display at the last step, once, and show 15 significant digits at most.
Frequently asked questions
Why is 0.1 + 0.2 not equal to 0.3?
Neither 0.1 nor 0.2 nor 0.3 can be stored exactly in binary, the same way 1/3 cannot be written exactly in decimal. Each is rounded to the nearest 64-bit value, and the two rounding errors in 0.1 and 0.2 add up to a sum that lands one step above the stored value of 0.3. Printed with 17 digits, the sum is 0.30000000000000004.
How many decimal digits is a JavaScript number good for?
About 15 to 17 significant decimal digits, according to MDN, because the fraction holds 53 binary digits. Anything beyond that is rounding noise, which is why a 20-digit printout of 0.1 shows a tail of 5s and 1s instead of zeros.
What is the largest safe integer?
9,007,199,254,740,991, which is 2 to the power 53 minus 1, available as Number.MAX_SAFE_INTEGER. Above it, not every integer has its own representation: 2 to the 53 and 2 to the 53 plus 1 are the same number in JavaScript. IDs that large should travel as strings or as BigInt.
How should I compare two floating point numbers?
Not with === unless both are integers or exact binary fractions. Compare the absolute difference against a tolerance that fits the size of the numbers: Number.EPSILON is fine around 1, MDN suggests scaling it, for example 2000 times EPSILON for values around 2000. Python offers math.isclose for the same purpose. For money, work in whole cents instead.
Why does (1.005).toFixed(2) give 1.00 and not 1.01?
Because the double nearest to 1.005 is slightly below 1.005, so the value being rounded is really 1.00499999999999989, which is below the halfway point. The same thing happens to 2.675 in Python. A rounding tool that works on the typed digits instead of the stored binary value gives 1.01.
Sources
- MDN Web Docs: Number (a double-precision 64-bit IEEE 754 value; 1 sign bit, 11 exponent bits, 52 mantissa bits; precision 2 to the minus 52, about 15 to 17 significant decimal digits; integers exact from minus 2 to the 53 plus 1 to 2 to the 53 minus 1; MAX_VALUE and Infinity)
- MDN Web Docs: Number.EPSILON (2 to the minus 52, about 2.22e-16, the gap between 1 and the next double; a reasonable threshold around magnitude 1 only, scale it for larger values, never compare floats with ===)
- MDN Web Docs: Number.MAX_SAFE_INTEGER (9007199254740991; safe means represented exactly and compared correctly; MAX_SAFE_INTEGER + 1 === MAX_SAFE_INTEGER + 2 is true)
- Python documentation: Floating-Point Arithmetic, Issues and Limitations (most decimal fractions have no exact binary form, like 1/3 in decimal; 0.1 is stored as 3602879701896397 / 2**55, exactly 0.1000000000000000055511151231257827021181583404541015625; 0.1 + 0.1 + 0.1 != 0.3; math.isclose, math.fsum, the decimal and fractions modules)
- Python documentation: built-in functions, round() (round(2.675, 2) gives 2.67 instead of the expected 2.68 because most decimal fractions cannot be represented exactly as a float; ties go to the even choice)