JavaScript Number Table
| Piece | What it does | Field note |
|---|---|---|
toFixed(n) | Fixed decimals, STRING | Format for display, never for math - it returns text |
parseInt(str, 10) | String to integer | ALWAYS pass the radix - parseInt('08') bit once via legacy octal |
parseFloat | String to float, lenient | Stops at first junk: parseFloat('12px') = 12 |
Number(str) | Strict conversion | Whole string or NaN - Number('12px') = NaN, the honest one |
Math.round / trunc | Round vs chop | round(-0.5) = -0 (toward +Infinity); trunc cuts toward zero |
Number.EPSILON | The comparison guard | 0.1 + 0.2 !== 0.3 - compare Math.abs(a-b) < EPSILON |
NaN !== NaN | The poison value | Number.isNaN(x) is the reliable check - global isNaN coerces first |
isSafeInteger | The 2^53-1 guard | Bigger integers silently lose precision - why JSON ids ship as strings |
JavaScript has ONE number type - IEEE 754 doubles - used for integers and floats alike. Every famous oddity follows from that single fact: 0.1 + 0.2 !== 0.3 is binary floating point doing its job, and integers are only safe up to 2^53 - 1, after which they corrupt silently.
Bottom line: parseInt always takes radix 10 (the historical '08' octal bug is why the radix argument exists), Number() is the strict parser (any junk becomes NaN) while parseFloat is the lenient one (stops at the first bad character), and toFixed returns a display STRING - formatting is never arithmetic.
The honest part: NaN is a poison value that infects every calculation it touches, fails all equality including itself, and the global isNaN() makes it worse by coercing its argument first - Number.isNaN() is the only check that asks the real question.
How to use
- Parse with intent: parseInt(input, 10) for integers, Number(input) when the whole string must be a number, parseFloat only when a trailing unit is expected.
- Compare floats honestly: Math.abs(a - b) < Number.EPSILON instead of === - or round to your actual precision before comparing.
- Validate money and ids: Number.isSafeInteger(total) guards arithmetic; treat any JSON id over 2^53 - 1 as a string end-to-end.
Frequently asked questions
Why does 0.1 + 0.2 not equal 0.3?
IEEE 754 doubles store numbers in binary fractions, and 0.1 has no exact binary representation - it is stored as the nearest double, a hair off. Adding two approximated values compounds the error: the result is 0.30000000000000004, which fails === against the differently-approximated literal 0.3. Nothing is broken; the decimal numbers you typed never existed in binary. The fixes: compare with a tolerance (Math.abs(a - b) < Number.EPSILON), round to your real precision (display cents, not raw floats), or use integer arithmetic for money (count cents).
What is the difference between parseInt, parseFloat and Number()?
Strictness. Number('12px') is NaN - the whole string must be a valid number, no partial credit. parseFloat('12px') is 12 - it parses the leading numeric portion and stops at the first character that cannot belong. parseInt('12.9') is 12 - integers only, truncating the decimal part. Choose by contract: user-typed fields that must be fully numeric get Number() (junk should fail loudly); CSS values and dimension strings get parseFloat; parseInt is for counting prefixes and always takes radix 10 - without it, legacy engines could interpret leading-zero strings as octal.
Why do big IDs from JSON APIs arrive corrupted?
The 2^53 - 1 line. JavaScript doubles store integers exactly only up to 9,007,199,254,740,991; beyond that, the least-significant bits have no room and the value silently rounds to a neighboring integer - no error, no NaN, just a wrong id that happens to still look plausible. Databases routinely generate 64-bit ids bigger than that, so the API contract becomes: ids travel as STRINGS, and JSON.parse on the JS side must not touch them. This is also the case for BigInt's existence - but the pragmatic fix in most apps is string ids end-to-end.
When is toFixed the wrong tool?
Whenever the result feeds more computation. toFixed returns a STRING - '1.35'.toFixed(1) gives '1.4', and '1.4' * 2 works only by accidental coercion; chained formatting-then-math hides rounding errors that compound. It also rounds by the binary representation, so half-way cases can surprise (1.005.toFixed(2) gives '1.00' because 1.005 is stored slightly below). Correct usage: toFixed at the DISPLAY boundary only; real rounding with Math.round(x * 100) / 100 or a decimal library; and never format inside a loop that keeps calculating.