Numbers

JavaScript gives you two number types, and they solve different problems.

  1. 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.

  2. 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
1.23e6
→ move the point 6 places right →
1 230 000
A positive exponent shifts the decimal point to the right.

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
1234e-2
→ move the point 2 places left →
12.34
A negative exponent shifts the decimal point to the 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.

decimal
255
hex
0xff
octal
0o377
binary
0b11111111
Four ways to write the number 255.

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..9 then A..F.

  • base 2 — inspecting the result of bitwise operations. Digits are 0 and 1.

  • base 36 — the ceiling, using every Latin letter (0..9 then A..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:

interactiveOne number, every base

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.floor

Rounds toward negative infinity (down): 3.13, and -1.1-2.

Math.ceil

Rounds toward positive infinity (up): 3.14, and -1.1-1.

Math.round

Rounds to the nearest integer. A .5 tie rounds toward positive infinity: 3.54, and -3.5-3.

Math.trunc

Drops the fractional part outright, no rounding: 3.13, -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.

343.6floor / truncceil / round-2-1-1.6floor / roundceil / trunc
For a positive number like 3.6 and a negative one like -1.6, each function picks a different neighbor.

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:

interactivefloor vs ceil vs round vs trunc

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.

1 bit
sign
11 bits
exponent
52 bits
mantissa (digits)
How IEEE-754 double precision splits its 64 bits.

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.

0.1
what you write
stored as →
0.10000000000000000555…
closest representable value
0.1 has no exact binary form, so it's rounded to the closest 64-bit value — a hair off.

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:

interactiveWhere the extra digits hide

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
NaN === NaN
false ✗
isNaN(NaN)
true ✓
NaN fails every equality check, so you need a function to detect it.

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
450msread → 450stop
parseInt reads left to right and stops at the first character it can't use.

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:

interactiveSoft parsing vs strict conversion

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...) and Math.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 n raised to power.

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 e and a zero count. 123e6 is 123 with 6 zeroes: 123000000.
  • A negative exponent divides by 1 with that many zeroes. 123e-6 is 0.000123 (123 millionths).
  • Underscores like 1_000_000 are 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, with 2 ≤ 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 for NaN.
  • Number.isNaN(value) — checks the argument is already a number, then tests for NaN.
  • isFinite(value) — converts to a number, then checks it isn’t NaN/Infinity/-Infinity.
  • Number.isFinite(value) — checks the argument is a number, then tests it’s finite.

Pulling a number out of "12pt" or "100px":

  • Use parseInt/parseFloat for 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, or num.toFixed(precision).
  • Keep the IEEE-754 precision loss in mind — 0.1 + 0.2 is not exactly 0.3.

More math:

  • Reach for the Math object. Small library, covers the basics.