Parsing Numbers
User input, query strings, file contents, and API responses all arrive as text. Before you can do arithmetic with them, you have to convert those strings into actual numbers — and JavaScript gives you several tools for the job, each with subtly different behavior. Picking the wrong one leads to silent bugs like "12px" becoming NaN or "08" parsing as 0. This page walks through Number(), parseInt(), parseFloat(), and the unary + operator so you always reach for the right one.
The four conversion tools
There are four idiomatic ways to turn a string into a number. They split into two camps: strict converters that reject anything that isn’t a clean number, and lenient parsers that read as much of the leading text as they can and ignore the rest.
| Tool | Style | Reads partial input? | Decimals? | Radix control |
|---|---|---|---|---|
Number(str) | Strict | No | Yes | No (decimal + 0x/0o/0b prefixes) |
+str (unary plus) | Strict | No | Yes | No |
parseInt(str, radix) | Lenient | Yes | No (integers only) | Yes |
parseFloat(str) | Lenient | Yes | Yes | No (decimal only) |
Number() and unary +
Number() converts the entire string. If any non-numeric character remains (other than surrounding whitespace), you get NaN. The unary + operator is functionally identical for strings — it’s just shorter and reads nicely inline.
Number("42"); // 42
Number("3.14"); // 3.14
Number(" 10 "); // 10 (whitespace trimmed)
Number(""); // 0 (empty string!)
Number("12px"); // NaN (trailing junk rejected)
Number("0xFF"); // 255 (understands hex prefix)
Number("1e3"); // 1000 (scientific notation)
+"42"; // 42
+"3.14"; // 3.14
+true; // 1
Because Number() is all-or-nothing, it’s the safest choice when you expect a clean numeric string and want a clear failure (NaN) otherwise.
Watch out:
Number("")returns0, notNaN. So doesNumber(null)andNumber(" "). Always validate user input separately rather than trusting a falsy-to-zero conversion.
parseInt() and the radix argument
parseInt() reads an integer from the start of a string and stops at the first character it doesn’t understand. That makes it perfect for values with units like "16px".
The second argument, radix, tells parseInt() which number base to use. You should almost always pass it explicitly.
parseInt("16px"); // 16 (stops at "p")
parseInt("3.99"); // 3 (truncates, no rounding)
parseInt(" 42 cats"); // 42
parseInt("FF", 16); // 255 (hexadecimal)
parseInt("1010", 2); // 10 (binary)
parseInt("hello"); // NaN (no leading digits)
Output:
16
3
42
255
10
NaN
Why pass the radix? Without it, parseInt() infers the base from the string’s prefix. Modern engines treat a leading 0 as decimal, but legacy behavior treated it as octal — and that ambiguity has caused real-world bugs (famously with zero-padded date strings).
parseInt("08"); // 8 in modern engines — but always specify the base
parseInt("08", 10); // 8 (unambiguous, correct)
parseInt("0x1F"); // 31 (auto-detects hex prefix)
Tip: Make
parseInt(str, 10)a habit. Linters like ESLint flag a missing radix (radixrule) precisely because the default behavior is surprising.
parseFloat()
parseFloat() is the lenient parser for decimal and scientific notation. Like parseInt(), it reads from the front and ignores trailing junk, but it keeps the fractional part.
parseFloat("3.14159"); // 3.14159
parseFloat("1.5rem"); // 1.5
parseFloat(" -0.75kg "); // -0.75
parseFloat("6.022e23"); // 6.022e+23
parseFloat("12.34.56"); // 12.34 (stops at 2nd dot)
parseFloat(".5"); // 0.5
There is no radix parameter — parseFloat() is decimal only.
Choosing the right one
Use this decision guide:
- Validating a complete numeric string (form field, config value) →
Number()or unary+. You wantNaNwhen the input is malformed. - Extracting a leading number from text with units (
"24px","1.5rem") →parseInt()for whole numbers,parseFloat()for decimals. - Parsing a non-decimal base (hex color, binary flag) →
parseInt(str, 16)/parseInt(str, 2). - Shortest inline coercion → unary
+.
Detecting failures with NaN
Every one of these returns NaN when it can’t produce a number. Since NaN is famously not equal to itself, never test with ===. Use Number.isNaN() (the strict, type-safe check) instead of the legacy global isNaN().
function toNumber(input) {
const n = Number(input);
return Number.isNaN(n) ? null : n;
}
console.log(toNumber("99.5")); // 99.5
console.log(toNumber("12px")); // null
console.log(toNumber(" ")); // 0 — Number trims whitespace
console.log(NaN === NaN); // false — never compare with ===
Output:
99.5
null
0
false
Best practices
- Reach for
Number()(or unary+) when the whole string must be a valid number, and treat theNaNresult as a validation failure. - Use
parseInt()/parseFloat()only when you intentionally want to read a leading number and discard a trailing suffix. - Always pass an explicit radix to
parseInt()—parseInt(value, 10)for ordinary decimal integers. - Check results with
Number.isNaN(), never with=== NaNor the loose globalisNaN(). - Remember
Number("")andNumber(null)are0; validate emptiness before converting if zero is a meaningful value. - For money and other precision-sensitive values, parse to a number only when you understand floating-point limits — see number precision below.