Numbers
JavaScript gives you two number types, and they solve different problems.
-
Regular numbers live in the 64-bit IEEE-754 format, often called “double precision floating point.” Almost every number you touch is one of these, and they’re the subject of this chapter.
-
BigInt numbers hold integers of any length. You reach for them when a value grows past the safe integer range for regular numbers — larger than
(253-1)or smaller than-(253-1), a limit we met back in Data types. BigInt shows up in a handful of specialized places, so it gets its own chapter: BigInt.
The rest of this page is about regular numbers. There’s more to them than 2 + 2.
More ways to write a number
Say you need one billion. The direct spelling works:
let billion = 1000000000;
Counting those zeroes by eye is error-prone. You can drop underscores between digits as visual separators:
let billion = 1_000_000_000;
The underscore is pure syntactic sugar. The engine skips over any _ sitting between digits, so this is the same billion as before — the separator exists only for your eyes.
Long runs of zeroes are still tedious. In writing we shorten them to things like "1bn" or "7.3bn". JavaScript has its own shorthand: append e and a count of zeroes.
let billion = 1e9; // 1 followed by 9 zeroes
alert( 7.3e9 ); // 7.3 billion (same as 7300000000 or 7_300_000_000)
Read eN as “multiply by 1 with N zeroes after it.”
1e3 === 1 * 1000; // e3 means * 1000
1.23e6 === 1.23 * 1000000; // e6 means * 1000000
Now the opposite direction. One microsecond is a millionth of a second:
let mcs = 0.000001;
Same trick, with a negative exponent. Count the leading zeroes and negate:
let mcs = 1e-6; // six zeroes counted to the left of the 1
A negative number after e means “divide by 1 with that many zeroes.”
// -3 divides by 1 with 3 zeroes
1e-3 === 1 / 1000; // 0.001
// -6 divides by 1 with 6 zeroes
1.23e-6 === 1.23 / 1000000; // 0.00000123
// works with any mantissa
1234e-2 === 1234 / 100; // 12.34, the point moves 2 places left
Hex, binary and octal numbers
Hexadecimal turns up all over JavaScript: CSS colors, character codes, byte-level work. Because it’s so common, there’s a literal syntax — 0x followed by the digits.
alert( 0xff ); // 255
alert( 0xFF ); // 255 (case of the hex digits doesn't matter)
Binary and octal are rarer, but they have literals too: 0b and 0o.
let a = 0b11111111; // 255 in binary
let b = 0o377; // 255 in octal
alert( a == b ); // true — both are the number 255
Only these three alternate bases get literal syntax. For any other base, you’ll parse from a string with parseInt, covered later in this chapter.
toString(base)
Going the other way — from a number to its written form in some base — is the job of num.toString(base). It returns a string.
let num = 255;
alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
base ranges from 2 to 36, defaulting to 10.
Where each base earns its keep:
-
base 16 — hex colors, character codes, byte dumps. Digits are
0..9thenA..F. -
base 2 — inspecting the result of bitwise operations. Digits are
0and1. -
base 36 — the ceiling, using every Latin letter (
0..9thenA..Z). It packs a number into the fewest characters, handy for shortening a long numeric id into a compact URL slug:alert( 604800..toString(36) ); // cyo0
Type any whole number and drag the base to watch the same value re-spell itself across all bases from 2 to 36:
Rounding
Chopping the fractional part off a number is one of the most common things you’ll do. JavaScript gives you four built-ins, and they differ in which direction they move.
Math.floorRounds toward negative infinity (down):
3.1→3, and-1.1→-2.
Math.ceilRounds toward positive infinity (up):
3.1→4, and-1.1→-1.
Math.roundRounds to the nearest integer. A
.5tie rounds toward positive infinity:3.5→4, and-3.5→-3.
Math.truncDrops the fractional part outright, no rounding:
3.1→3,-1.1→-1. It’s the only one that always pulls toward zero.
The clearest way to feel the difference is to picture the number line. floor always steps left, ceil always steps right, trunc always steps toward zero, and round steps to whichever integer is closer.
Here’s the full comparison across a spread of inputs:
Math.floor |
Math.ceil |
Math.round |
Math.trunc |
|
|---|---|---|---|---|
3.1 |
3 |
4 |
3 |
3 |
3.5 |
3 |
4 |
4 |
3 |
3.6 |
3 |
4 |
4 |
3 |
-1.1 |
-2 |
-1 |
-1 |
-1 |
-1.5 |
-2 |
-1 |
-1 |
-1 |
-1.6 |
-2 |
-1 |
-2 |
-1 |
Slide the value below — especially across zero — and watch where each of the four functions lands:
Those four handle the integer part. But often you want to keep some decimals — round 1.2345 down to 1.23. Two approaches.
1. Multiply, round, divide.
Scale the number up so the digits you care about land in the integer position, round, then scale back down.
let num = 2.71828;
alert( Math.round(num * 100) / 100 ); // 2.71828 → 271.828 → 272 → 2.72
2. toFixed(n). This method rounds to n digits after the point and hands back a string. See its full reference on MDN.
let num = 8.13;
alert( num.toFixed(1) ); // "8.1"
It rounds to the nearest value, like Math.round:
let num = 8.27;
alert( num.toFixed(1) ); // "8.3"
When the number has fewer decimals than you asked for, toFixed pads with zeroes — useful for money displays where you always want two places:
let num = 8.13;
alert( num.toFixed(5) ); // "8.13000"
Since the result is a string, coerce it back to a number when you need to keep computing — a unary plus or Number() does it: +num.toFixed(5).
Imprecise calculations
Internally, a regular number is 64 bits: 52 bits for the significant digits (the mantissa), 11 bits for the position of the point (the exponent), and 1 bit for the sign.
Push a number past what those bits can hold and it doesn’t error — it becomes the special value Infinity:
alert( 1e500 ); // Infinity
The trap that bites more often is subtler: loss of precision.
Look at this equality check, which is false:
alert( 0.1 + 0.2 == 0.3 ); // false
So if 0.1 + 0.2 isn’t 0.3, what is it?
alert( 0.1 + 0.2 ); // 0.30000000000000004
Picture a shopping cart with a $0.10 item and a $0.20 item. The total renders as $0.30000000000000004. Not the invoice anyone expects.
Why does this happen? A number is stored in binary — a run of ones and zeroes. Fractions that look tidy in decimal, like 0.1 and 0.2, turn into infinitely repeating fractions in binary.
alert( 0.1.toString(2) ); // 0.0001100110011001100110011001100110011001100110011001101
alert( 0.2.toString(2) ); // 0.001100110011001100110011001100110011001100110011001101
alert( (0.1 + 0.2).toString(2) ); // 0.0100110011001100110011001100110011001100110011001101
Think about 0.1. It’s 1/10. Decimal represents tenths cleanly. Now try 1/3 in decimal: 0.33333…, endless. A fraction is finite in a given base only when its denominator divides a power of that base. Decimal (base 10) handles denominators built from 2 and 5. Binary (base 2) handles only powers of 2 — so 1/10 runs forever, exactly the way 1/3 runs forever in decimal.
There is no way to store exactly 0.1 or 0.2 in binary, just as there’s no way to write one-third exactly as a decimal.
IEEE-754 copes by rounding to the nearest storable number. The rounding is usually invisible, but it’s there. Ask for enough digits and it surfaces:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
Add two of these slightly-off values and their errors compound. That’s the whole story behind 0.1 + 0.2 !== 0.3.
Can you work around it? The most dependable fix is to round the result with toFixed(n):
let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // "0.30"
Remember toFixed returns a string, and here it guarantees two decimals — perfect for displaying $0.30. When you need a number back, prefix a unary plus:
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3
Another tactic: scale up to integers, compute, scale down. Working in whole numbers shrinks the error, though the final division can reintroduce a little:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100 ); // 0.4200000000000001
So scaling helps but isn’t a cure. Sometimes you can sidestep fractions entirely — a shop can store prices in cents as integers. Apply a 30% discount, though, and fractions creep back. Fully avoiding them is rare; more often you just round to trim the tail when it matters.
Add two decimals of your own below. The raw sum shows all the hidden digits; toFixed(2) cleans it up for display:
Tests: isFinite and isNaN
Two special numeric values deserve their own checks:
Infinity(and-Infinity) — larger (or smaller) than any finite number.NaN— “not a number,” the marker for a failed numeric operation.
Both are of type number, yet neither behaves like an ordinary one, so there are dedicated tests.
isNaN(value) converts its argument to a number, then reports whether the result is NaN:
alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
Why not just compare with === NaN? Because NaN is famously unequal to everything, itself included:
alert( NaN === NaN ); // false
isFinite(value) converts to a number and returns true only for an ordinary, finite number — not NaN, Infinity, or -Infinity:
alert( isFinite("15") ); // true
alert( isFinite("str") ); // false — becomes NaN
alert( isFinite(Infinity) ); // false — a special value
A common use is validating that a string holds a real number:
let num = +prompt("Enter a number", '');
// true unless you enter Infinity, -Infinity, or something non-numeric
alert( isFinite(num) );
One gotcha: an empty string or a string of only spaces converts to 0 in every numeric function, isFinite included. So isFinite("") is true.
parseInt and parseFloat
Converting with unary + or Number() is all-or-nothing. One stray character and you get NaN:
alert( +"100px" ); // NaN
(The one thing it tolerates is leading and trailing whitespace, which it ignores.)
Real data is messier — "100px" and "12pt" from CSS, or "19€" where the currency sign trails the amount. You want the number and don’t care about the units. parseInt and parseFloat do exactly that.
They read characters from the start of the string, accumulating a number, and stop at the first character that can’t belong to one. Whatever they’ve gathered so far is the result. parseInt yields an integer; parseFloat allows one decimal point.
alert( parseInt('450ms') ); // 450
alert( parseFloat('64.5kg') ); // 64.5
alert( parseInt('64.9') ); // 64 — stops at the dot, integer only
alert( parseFloat('64.9.3') ); // 64.9 — stops at the second dot
If the very first meaningful character isn’t a digit, there’s nothing to gather and you get NaN:
alert( parseInt('x450') ); // NaN — the first symbol halts it immediately
Try your own messy strings. Watch how parseInt stops at the first non-integer character while parseFloat allows a single decimal point, and how strict Number() refuses anything with leftover junk:
Other math functions
The built-in Math object bundles a compact library of math functions and constants.
A taste:
Math.random()Returns a random number in
[0, 1)— zero is possible, one is not.alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... a fresh value each call
Math.max(a, b, c...)andMath.min(a, b, c...)Return the largest and smallest of any number of arguments.
alert( Math.max(4, 9, -7, 0, 2) ); // 9 alert( Math.min(6, 2) ); // 2
Math.pow(n, power)Returns
nraised topower.alert( Math.pow(3, 4) ); // 3 to the 4th = 81
Math holds much more — trigonometry, logarithms, square roots, the constants PI and E, and so on. The full list lives in the Math reference.
Summary
Writing numbers with lots of zeroes:
- Append
eand a zero count.123e6is123with 6 zeroes:123000000. - A negative exponent divides by 1 with that many zeroes.
123e-6is0.000123(123 millionths). - Underscores like
1_000_000are ignored by the engine and exist purely for readability.
Different numeral systems:
- Literals exist for hex (
0x), octal (0o), and binary (0b). parseInt(str, base)reads a string into an integer in the given base, with2 ≤ base ≤ 36.num.toString(base)writes a number as a string in the given base.
Testing for special values:
isNaN(value)— converts to a number, then checks forNaN.Number.isNaN(value)— checks the argument is already anumber, then tests forNaN.isFinite(value)— converts to a number, then checks it isn’tNaN/Infinity/-Infinity.Number.isFinite(value)— checks the argument is anumber, then tests it’s finite.
Pulling a number out of "12pt" or "100px":
- Use
parseInt/parseFloatfor a “soft” read that returns whatever number it managed before hitting a bad character.
Fractions:
- Round with
Math.floor,Math.ceil,Math.trunc,Math.round, ornum.toFixed(precision). - Keep the IEEE-754 precision loss in mind —
0.1 + 0.2is not exactly0.3.
More math:
- Reach for the Math object. Small library, covers the basics.