JavaScript has only one type of number for everything: whole numbers, decimals, negatives, and very large or very small values. That keeps things simple, but it also explains most of the surprises in this lesson, from 0.1 + 0.2 to numbers that quietly round themselves.
Numbers can be written with or without decimals:
let a = 42; // whole number
let b = 3.14; // decimal
let c = -7; // negative
let d = 1_000_000; // underscores for readability: same as 1000000
console.log(typeof a, typeof b, d);
Extra large or extra small numbers can be written in scientific (exponent) notation, where e means "times ten to the power of":
console.log(123e5); // 123 × 10⁵
console.log(123e-5); // 123 × 10⁻⁵
Many programming languages have several number types: whole-number types of different sizes (byte, short, int, long) and decimal types (float, double). JavaScript has just one. Every number is stored as a 64-bit floating point number, following an international standard called IEEE 754. Those 64 bits are split three ways:
| Part | Bits | Holds |
|---|---|---|
| Fraction (mantissa) | 52 bits (0 to 51) | the digits |
| Exponent | 11 bits (52 to 62) | where the decimal point goes |
| Sign | 1 bit (63) | positive or negative |
You never deal with those bits directly. What matters is the consequence: with only 52 bits for the digits, there is a limit to how precise a number can be.
Whole numbers are accurate up to about 15 digits. Past that, they get rounded:
let x = 999999999999999; // 15 digits
let y = 9999999999999999; // 16 digits
console.log(x);
console.log(y);
No error, no warning: the 16-digit number is simply wrong. The exact limit is 9,007,199,254,740,991, covered in JS Number Properties, and the fix for bigger numbers is JS BigInt.
Decimals have a similar problem. Computers store them in binary, and some simple decimals (like 0.1) cannot be stored exactly in binary, just as 1/3 cannot be written exactly as a decimal (0.3333...). The tiny error shows up in a famous surprise:
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
This is not a JavaScript bug; almost every programming language does the same thing. Three common fixes:
// 1. Multiply to whole numbers first, then divide back
console.log((0.2 * 10 + 0.1 * 10) / 10);
// 2. Round only when displaying
console.log((0.1 + 0.2).toFixed(2));
// 3. Work in whole units: cents instead of dollars
let cents = 10 + 20;
console.log(cents / 100);
JavaScript uses + for both adding and joining. Numbers are added; if either side is a string, they are joined:
let x = 10;
let y = 20;
console.log(x + y); // two numbers
console.log("10" + "20"); // two strings
console.log(10 + "20"); // number and string
Two classic mistakes come from JavaScript working left to right:
let x = 10;
let y = 20;
let z = "30";
console.log("The result is: " + x + y); // expecting 30?
console.log(x + y + z); // expecting 102030?
"The result is: " + 10 is a string, and then 20 is joined on. Fix it with parentheses: "The result is: " + (x + y), or a template string10 + 20 is added first (both numbers, so 30), and only then is "30" joined onWith every other math operator, JavaScript converts numeric strings to numbers:
let x = "100";
let y = "10";
console.log(x / y); // works
console.log(x * y); // works
console.log(x - y); // works
console.log(x + y); // joins instead!
+ breaks the pattern. Convert on purpose with Number() before doing math with values that might be strings (see JS Number Methods).
NaN is a reserved word meaning "this is not a legal number". It is what you get when math cannot produce a real number:
console.log(100 / "Apple"); // not numeric
console.log(100 / "10"); // numeric string: fine
console.log(0 / 0);
console.log(typeof NaN); // oddly enough
NaN spreads. Any math with it gives NaN, and joining it with a string gives text containing "NaN":
console.log(NaN + 5);
console.log(NaN + "5");
console.log(NaN === NaN); // not even equal to itself!
Because NaN is not equal to anything, you cannot test for it with ===. Use isNaN(), or better, Number.isNaN() (JS Number Methods explains the difference):
let result = 100 / "Apple";
console.log(isNaN(result));
console.log(Number.isNaN(result));
Infinity (or -Infinity) is what JavaScript gives you when a number goes beyond the largest number it can store. Squaring a number over and over gets there surprisingly fast:
let myNumber = 2;
while (myNumber != Infinity) {
myNumber = myNumber * myNumber;
console.log(myNumber);
}
Dividing by zero does not crash either. It quietly gives Infinity, which then spreads through later math:
console.log(2 / 0);
console.log(-2 / 0);
console.log(typeof Infinity);
By default, numbers are written and shown in base 10. Prefixes let you write them in other bases:
console.log(0xFF); // hexadecimal (base 16)
console.log(0b1010); // binary (base 2)
console.log(0o17); // octal (base 8)
Hex values show up constantly in CSS colors: #FF8800 is three hex numbers for red, green, and blue. Going the other way, toString(base) writes a number in any base from 2 to 36:
let myNumber = 32;
console.log(myNumber.toString(16)); // hex
console.log(myNumber.toString(8)); // octal
console.log(myNumber.toString(2)); // binary
0 as octal. So 010 is not ten, it is 8. (In strict mode, it is a SyntaxError instead.) This bites people who pad numbers to line them up, like zip codes or times: let minutes = 010;. Write 10, or keep padded values as strings: "010".
Like strings and booleans, numbers can be created as objects with new. Never do this:
let x = 500;
let y = new Number(500);
let z = new Number(500);
console.log(typeof y);
console.log(x == y);
console.log(x === y);
console.log(y == z); // two objects are never equal
Same story as new String() and new Boolean(): slower code and broken comparisons.
What does this log?
let a = 5, b = 5;
console.log("Total: " + a + b);
Total: 55. Left to right: the string comes first, so each number is joined on as text. Use "Total: " + (a + b) to get Total: 10.How should you check whether result is NaN?
result === NaNresult == NaNNumber.isNaN(result)typeof result === "NaN"NaN is not equal to anything, including itself, so A and B are always false. D is always false too, because typeof NaN is "number".What is console.log(010 + 1)?
119"0101"NaN010 an octal number, which is 8. 8 + 1 is 9. Never start a number with 0."5" + 5, "5" - 5, "5" * "5", and 5 + 5 + "5"9999999999999999 and 99999999999999999. How far off are they?toString(). Then log 0xFF, 0o377, and 0b11111111