Most languages separate integers from decimals. JavaScript has only one type, which simplifies a great deal and complicates one thing: the precision of calculations on fractional values.
This is where the famous 0.1 + 0.2 that does not give 0.3 comes from. It is neither a bug nor a quirk of the language, and knowing its origin saves an evening spent staring at a cart total.
Definition
Number is the type representing numbers in JavaScript, integers and decimals alike, as a 64-bit floating point value. It covers integers exactly up to roughly nine quadrillion, a limit exposed by Number.MAX_SAFE_INTEGER.
console.log(typeof 49, typeof 4.9); // "number" "number"
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
console.log((0.1 + 0.2).toFixed(2)); // "0.30", a string
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991The gap comes from binary representation: some decimals, one tenth among them, have no exact form in base 2, exactly as one third has none in base 10. The error is tiny but real, and it accumulates.
For money, the safest practice is to count in cents, therefore in whole numbers, and to divide only when displaying. A total computed in decimal currency eventually produces a phantom cent.
Converting and checking
User input always arrives as a String. Converting it is the first thing to do, and checking the result the second.
| Call | Result |
|---|---|
Number("49") | 49 |
Number("49 usd") | NaN |
parseInt("49 usd", 10) | 49 |
Number("") | 0 |
Number(null) | 0 |
The last two lines are classic traps: a missing value becomes zero without the slightest warning. That is a direct effect of Coercion (type conversion), and the only protection is to test for presence before converting.
const input = "49 usd";
const value = Number(input);
if (Number.isNaN(value)) {
console.log("invalid input");
} else {
console.log(value * 2);
}Two special values
NaNsignals an impossible calculation. It equals nothing, not even itself, so onlyNumber.isNaN()detects it reliably.Infinityshows up on division by zero. The language raises no error, it returns this value, which then contaminates every following calculation.
Frequently asked questions
How do I display a price properly?
With Intl.NumberFormat, which handles the currency, the decimal separator and spacing according to the locale. new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(49) returns "$49.00". That beats a toFixed followed by concatenation, which produces the wrong format as soon as the country changes.
What do I do beyond MAX_SAFE_INTEGER?
The BigInt type exists for that: you write 9007199254740993n, with a trailing n. You mostly meet it on identifiers coming from a database, which then have to travel as text rather than as numbers so they are not rounded on the way.
Does toFixed return a number?
No, it returns a string, which surprises many people. Chaining a calculation onto it triggers concatenation instead of addition. If the result feeds another calculation, convert it back with Number(), or better, keep the number and round only at display time. See also Math for rounding that stays numeric.