Course outline · 0% complete

0/29 lessons0%

Course overview →

Parsing and formatting numbers

lesson 8-2 · ~9 min · 26/29

Text in, numbers out

User input and file data arrive as strings. Python used int("42") and float("3.5"). JavaScript gives you:

  • Number("42")42. Strict: the whole string must be numeric, otherwise you get NaN.
  • parseInt("42px", 10)42. Forgiving: reads leading digits and stops. The 10 means base ten, always pass it.
  • parseFloat("3.5kg")3.5. Same idea with decimals.

NaN ("not a number") is the result of failed number math. It is contagious (NaN + 1 is NaN) and weird: it is not even equal to itself. Test for it with Number.isNaN(x), never with ===.

Going the other way, toFixed formats with a fixed number of decimals: (3.14159).toFixed(2) gives "3.14". Careful: it returns a string, ready for display, not for more math.

One more classic: 0.1 + 0.2 prints 0.30000000000000004. Computers store decimals in binary, so tiny rounding errors appear. Python does the exact same thing. Format with toFixed when showing money.

Strict conversion, forgiving conversion, and rounding

Five lines covering the whole lesson: the difference between Number and parseInt, what a failed conversion produces, the binary rounding surprise, and the fix for display.

console.log(Number("42"));
console.log(Number("abc"));
console.log(parseInt("42px", 10));
console.log(0.1 + 0.2);
console.log((0.1 + 0.2).toFixed(2));

Output

42
NaN
42
0.30000000000000004
0.30

Number("42") succeeds because every character is part of a number, while Number("abc") fails and produces NaN. parseInt("42px", 10) reads digits from the front and stops at the first character that cannot belong, which is what makes it useful for values like CSS sizes and forgiving for values you would rather have rejected.

The fourth line is the famous one. 0.1 and 0.2 cannot be stored exactly in binary, any more than one third can be written exactly in decimal, so their sum lands a hair away from 0.3. The fifth line rounds it for display, and note that 0.30 keeps its trailing zero because toFixed produces text rather than a number.

Adding two prices that arrived as text

Prices from a form or a file are strings, and this is the safe order of operations: convert first, add second, format last.

const a = "19.99";
const b = "5.50";

const total = Number(a) + Number(b);
console.log(total.toFixed(2));

Output

25.49

Converting first is not optional, because + between two strings means concatenation. Skipping the conversions would give "19.99" + "5.50", which is the string "19.995.50", a value that looks broken but raises no error at all.

The toFixed(2) at the end handles the display, and calling it last is deliberate. All arithmetic happens on real numbers, and formatting is the final step before the value is shown, which keeps rounded intermediate values from creeping into later math.

What toFixed hands back

For const price = (9.5).toFixed(2);, the value of typeof price is "string". The result is the text "9.50", not the number 9.50.

That distinction has real consequences, because + on a string concatenates instead of adding. Continuing to calculate with a toFixed result gives values like "9.502" where 11.5 was expected, and again with no error to point at the cause.

The discipline that avoids it is to keep the two phases separate.

PhaseWork withExample
inputNumber(...) or parseFloat(...)Number("19.99")
calculationplain numberssubtotal * 1.07
displaytoFixed, template literals` $${total.toFixed(2)} `

Testing for a failed conversion needs Number.isNaN(x) rather than x === NaN. NaN is the one value in JavaScript that is not equal to itself, so the === comparison is always false.