JavaScript for impatient programmers (ES2022 edition)
Please support this book: buy it or donate
(Ad, please don’t block.)

16 Numbers



JavaScript has two kinds of numeric values:

This chapter covers numbers. Bigints are covered later in this book.

16.1 Numbers are used for both floating point numbers and integers

The type number is used for both integers and floating point numbers in JavaScript:

98
123.45

However, all numbers are doubles, 64-bit floating point numbers implemented according to the IEEE Standard for Floating-Point Arithmetic (IEEE 754).

Integer numbers are simply floating point numbers without a decimal fraction:

> 98 === 98.0
true

Note that, under the hood, most JavaScript engines are often able to use real integers, with all associated performance and storage size benefits.

16.2 Number literals

Let’s examine literals for numbers.

16.2.1 Integer literals

Several integer literals let us express integers with various bases:

// Binary (base 2)
assert.equal(0b11, 3); // ES6

// Octal (base 8)
assert.equal(0o10, 8); // ES6

// Decimal (base 10)
assert.equal(35, 35);

// Hexadecimal (base 16)
assert.equal(0xE7, 231);

16.2.2 Floating point literals

Floating point numbers can only be expressed in base 10.

Fractions:

> 35.0
35

Exponent: eN means ×10N

> 3e2
300
> 3e-2
0.03
> 0.3e2
30

16.2.3 Syntactic pitfall: properties of integer literals

Accessing a property of an integer literal entails a pitfall: If the integer literal is immediately followed by a dot, then that dot is interpreted as a decimal dot:

7.toString(); // syntax error

There are four ways to work around this pitfall:

7.0.toString()
(7).toString()
7..toString()
7 .toString()  // space before dot

16.2.4 Underscores (_) as separators in number literals [ES2021]

Grouping digits to make long numbers more readable has a long tradition. For example:

Since ES2021, we can use underscores as separators in number literals:

const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;

With other bases, grouping is important, too:

const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;

We can also use the separator in fractions and exponents:

const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
16.2.4.1 Where can we put separators?

The locations of separators are restricted in two ways:

The motivation behind these restrictions is to keep parsing simple and to avoid strange edge cases.

16.2.4.2 Parsing numbers with separators

The following functions for parsing numbers do not support separators:

For example:

> Number('123_456')
NaN
> Number.parseInt('123_456')
123

The rationale is that numeric separators are for code. Other kinds of input should be processed differently.

16.3 Arithmetic operators

16.3.1 Binary arithmetic operators

Tbl. 5 lists JavaScript’s binary arithmetic operators.

Table 5: Binary arithmetic operators.
Operator Name Example
n + m Addition ES1 3 + 4 7
n - m Subtraction ES1 9 - 1 8
n * m Multiplication ES1 3 * 2.25 6.75
n / m Division ES1 5.625 / 5 1.125
n % m Remainder ES1 8 % 5 3
-8 % 5 -3
n ** m Exponentiation ES2016 4 ** 2 16
16.3.1.1 % is a remainder operator

% is a remainder operator, not a modulo operator. Its result has the sign of the first operand:

> 5 % 3
2
> -5 % 3
-2

For more information on the difference between remainder and modulo, see the blog post “Remainder operator vs. modulo operator (with JavaScript code)” on 2ality.

16.3.2 Unary plus (+) and negation (-)

Tbl. 6 summarizes the two operators unary plus (+) and negation (-).

Table 6: The operators unary plus (+) and negation (-).
Operator Name Example
+n Unary plus ES1 +(-7) -7
-n Unary negation ES1 -(-7) 7

Both operators coerce their operands to numbers:

> +'5'
5
> +'-12'
-12
> -'9'
-9

Thus, unary plus lets us convert arbitrary values to numbers.

16.3.3 Incrementing (++) and decrementing (--)

The incrementation operator ++ exists in a prefix version and a suffix version. In both versions, it destructively adds one to its operand. Therefore, its operand must be a storage location that can be changed.

The decrementation operator -- works the same, but subtracts one from its operand. The next two examples explain the difference between the prefix and the suffix version.

Tbl. 7 summarizes the incrementation and decrementation operators.

Table 7: Incrementation operators and decrementation operators.
Operator Name Example
v++ Increment ES1 let v=0; [v++, v] [0, 1]
++v Increment ES1 let v=0; [++v, v] [1, 1]
v-- Decrement ES1 let v=1; [v--, v] [1, 0]
--v Decrement ES1 let v=1; [--v, v] [0, 0]

Next, we’ll look at examples of these operators in use.

Prefix ++ and prefix -- change their operands and then return them.

let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);

let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);

Suffix ++ and suffix -- return their operands and then change them.

let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);

let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
16.3.3.1 Operands: not just variables

We can also apply these operators to property values:

const obj = { a: 1 };
++obj.a;
assert.equal(obj.a, 2);

And to Array elements:

const arr = [ 4 ];
arr[0]++;
assert.deepEqual(arr, [5]);

  Exercise: Number operators

exercises/numbers-math/is_odd_test.mjs

16.4 Converting to number

These are three ways of converting values to numbers:

Recommendation: use the descriptive Number(). Tbl. 8 summarizes how it works.

Table 8: Converting values to numbers.
x Number(x)
undefined NaN
null 0
boolean false 0, true 1
number x (no change)
bigint -1n -1, 1n 1, etc.
string '' 0
Other parsed number, ignoring leading/trailing whitespace
symbol Throws TypeError
object Configurable (e.g. via .valueOf())

Examples:

assert.equal(Number(123.45), 123.45);

assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);

assert.equal(Number(-123n), -123);

How objects are converted to numbers can be configured – for example, by overriding .valueOf():

> Number({ valueOf() { return 123 } })
123

  Exercise: Converting to number

exercises/numbers-math/parse_number_test.mjs

16.5 Error values

Two number values are returned when errors happen:

16.5.1 Error value: NaN

NaN is an abbreviation of “not a number”. Ironically, JavaScript considers it to be a number:

> typeof NaN
'number'

When is NaN returned?

NaN is returned if a number can’t be parsed:

> Number('$$$')
NaN
> Number(undefined)
NaN

NaN is returned if an operation can’t be performed:

> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN

NaN is returned if an operand or argument is NaN (to propagate errors):

> NaN - 3
NaN
> 7 ** NaN
NaN
16.5.1.1 Checking for NaN

NaN is the only JavaScript value that is not strictly equal to itself:

const n = NaN;
assert.equal(n === n, false);

These are several ways of checking if a value x is NaN:

const x = NaN;

assert.equal(Number.isNaN(x), true); // preferred
assert.equal(Object.is(x, NaN), true);
assert.equal(x !== x, true);

In the last line, we use the comparison quirk to detect NaN.

16.5.1.2 Finding NaN in Arrays

Some Array methods can’t find NaN:

> [NaN].indexOf(NaN)
-1

Others can:

> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN

Alas, there is no simple rule of thumb. We have to check for each method how it handles NaN.

16.5.2 Error value: Infinity

When is the error value Infinity returned?

Infinity is returned if a number is too large:

> Math.pow(2, 1023)
8.98846567431158e+307
> Math.pow(2, 1024)
Infinity

Infinity is returned if there is a division by zero:

> 5 / 0
Infinity
> -5 / 0
-Infinity
16.5.2.1 Infinity as a default value

Infinity is larger than all other numbers (except NaN), making it a good default value:

function findMinimum(numbers) {
  let min = Infinity;
  for (const n of numbers) {
    if (n < min) min = n;
  }
  return min;
}

assert.equal(findMinimum([5, -1, 2]), -1);
assert.equal(findMinimum([]), Infinity);
16.5.2.2 Checking for Infinity

These are two common ways of checking if a value x is Infinity:

const x = Infinity;

assert.equal(x === Infinity, true);
assert.equal(Number.isFinite(x), false);

  Exercise: Comparing numbers

exercises/numbers-math/find_max_test.mjs

16.6 The precision of numbers: careful with decimal fractions

Internally, JavaScript floating point numbers are represented with base 2 (according to the IEEE 754 standard). That means that decimal fractions (base 10) can’t always be represented precisely:

> 0.1 + 0.2
0.30000000000000004
> 1.3 * 3
3.9000000000000004
> 1.4 * 100000000000000
139999999999999.98

We therefore need to take rounding errors into consideration when performing arithmetic in JavaScript.

Read on for an explanation of this phenomenon.

  Quiz: basic

See quiz app.

16.7 (Advanced)

All remaining sections of this chapter are advanced.

16.8 Background: floating point precision

In JavaScript, computations with numbers don’t always produce correct results – for example:

> 0.1 + 0.2
0.30000000000000004

To understand why, we need to explore how JavaScript represents floating point numbers internally. It uses three integers to do so, which take up a total of 64 bits of storage (double precision):

Component Size Integer range
Sign 1 bit [0, 1]
Fraction 52 bits [0, 252−1]
Exponent 11 bits [−1023, 1024]

The floating point number represented by these integers is computed as follows:

(–1)sign × 0b1.fraction × 2exponent

This representation can’t encode a zero because its second component (involving the fraction) always has a leading 1. Therefore, a zero is encoded via the special exponent −1023 and a fraction 0.

16.8.1 A simplified representation of floating point numbers

To make further discussions easier, we simplify the previous representation:

The new representation works like this:

mantissa × 10exponent

Let’s try out this representation for a few floating point numbers.

Representations with negative exponents can also be written as fractions with positive exponents in the denominators:

> 15 * (10 ** -1) === 15 / (10 ** 1)
true
> 25 * (10 ** -2) === 25 / (10 ** 2)
true

These fractions help with understanding why there are numbers that our encoding cannot represent:

To conclude our excursion, we switch back to base 2:

Now we can see why 0.1 + 0.2 doesn’t produce a correct result: internally, neither of the two operands can be represented precisely.

The only way to compute precisely with decimal fractions is by internally switching to base 10. For many programming languages, base 2 is the default and base 10 an option. For example, Java has the class BigDecimal and Python has the module decimal. There are plans to add something similar to JavaScript: the ECMAScript proposal “Decimal”.

16.9 Integer numbers in JavaScript

Integer numbers are normal (floating point) numbers without decimal fractions:

> 1 === 1.0
true
> Number.isInteger(1.0)
true

In this section, we’ll look at a few tools for working with these pseudo-integers. JavaScript also supports bigints, which are real integers.

16.9.1 Converting to integer

The recommended way of converting numbers to integers is to use one of the rounding methods of the Math object:

For more information on rounding, consult §17.3 “Rounding”.

16.9.2 Ranges of integer numbers in JavaScript

These are important ranges of integer numbers in JavaScript:

16.9.3 Safe integers

This is the range of integer numbers that are safe in JavaScript (53 bits plus a sign):

[–(253)+1, 253–1]

An integer is safe if it is represented by exactly one JavaScript number. Given that JavaScript numbers are encoded as a fraction multiplied by 2 to the power of an exponent, higher integers can also be represented, but then there are gaps between them.

For example (18014398509481984 is 254):

> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988

The following properties of Number help determine if an integer is safe:

assert.equal(Number.MAX_SAFE_INTEGER, (2 ** 53) - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);

assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);

  Exercise: Detecting safe integers

exercises/numbers-math/is_safe_integer_test.mjs

16.9.3.1 Safe computations

Let’s look at computations involving unsafe integers.

The following result is incorrect and unsafe, even though both of its operands are safe:

> 9007199254740990 + 3
9007199254740992

The following result is safe, but incorrect. The first operand is unsafe; the second operand is safe:

> 9007199254740995 - 10
9007199254740986

Therefore, the result of an expression a op b is correct if and only if:

isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)

That is, both operands and the result must be safe.

16.10 Bitwise operators

16.10.1 Internally, bitwise operators work with 32-bit integers

Internally, JavaScript’s bitwise operators work with 32-bit integers. They produce their results in the following steps:

16.10.1.1 The types of operands and results

For each bitwise operator, this book mentions the types of its operands and its result. Each type is always one of the following two:

Type Description Size Range
Int32 signed 32-bit integer 32 bits incl. sign [−231, 231)
Uint32 unsigned 32-bit integer 32 bits [0, 232)

Considering the previously mentioned steps, I recommend to pretend that bitwise operators internally work with unsigned 32-bit integers (step “computation”) and that Int32 and Uint32 only affect how JavaScript numbers are converted to and from integers (steps “input” and “output”).

16.10.1.2 Displaying JavaScript numbers as unsigned 32-bit integers

While exploring the bitwise operators, it occasionally helps to display JavaScript numbers as unsigned 32-bit integers in binary notation. That’s what b32() does (whose implementation is shown later):

assert.equal(
  b32(-1),
  '11111111111111111111111111111111');
assert.equal(
  b32(1),
  '00000000000000000000000000000001');
assert.equal(
  b32(2 ** 31),
  '10000000000000000000000000000000');

16.10.2 Bitwise Not

Table 9: The bitwise Not operator.
Operation Name Type signature
~num Bitwise Not, ones’ complement Int32 Int32 ES1

The bitwise Not operator (tbl. 9) inverts each binary digit of its operand:

> b32(~0b100)
'11111111111111111111111111111011'

This so-called ones’ complement is similar to a negative for some arithmetic operations. For example, adding an integer to its ones’ complement is always -1:

> 4 + ~4
-1
> -11 + ~-11
-1

16.10.3 Binary bitwise operators

Table 10: Binary bitwise operators.
Operation Name Type signature
num1 & num2 Bitwise And Int32 × Int32 Int32 ES1
num1 ¦ num2 Bitwise Or Int32 × Int32 Int32 ES1
num1 ^ num2 Bitwise Xor Int32 × Int32 Int32 ES1

The binary bitwise operators (tbl. 10) combine the bits of their operands to produce their results:

> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'

16.10.4 Bitwise shift operators

Table 11: Bitwise shift operators.
Operation Name Type signature
num << count Left shift Int32 × Uint32 Int32 ES1
num >> count Signed right shift Int32 × Uint32 Int32 ES1
num >>> count Unsigned right shift Uint32 × Uint32 Uint32 ES1

The shift operators (tbl. 11) move binary digits to the left or to the right:

> (0b10 << 1).toString(2)
'100'

>> preserves highest bit, >>> doesn’t:

> b32(0b10000000000000000000000000000010 >> 1)
'11000000000000000000000000000001'
> b32(0b10000000000000000000000000000010 >>> 1)
'01000000000000000000000000000001'

16.10.5 b32(): displaying unsigned 32-bit integers in binary notation

We have now used b32() a few times. The following code is an implementation of it:

/**
 * Return a string representing n as a 32-bit unsigned integer,
 * in binary notation.
 */
function b32(n) {
  // >>> ensures highest bit isn’t interpreted as a sign
  return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
  b32(6),
  '00000000000000000000000000000110');

n >>> 0 means that we are shifting n zero bits to the right. Therefore, in principle, the >>> operator does nothing, but it still coerces n to an unsigned 32-bit integer:

> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1

16.11 Quick reference: numbers

16.11.1 Global functions for numbers

JavaScript has the following four global functions for numbers:

However, it is better to use the corresponding methods of Number (Number.isFinite(), etc.), which have fewer pitfalls. They were introduced with ES6 and are discussed below.

16.11.2 Static properties of Number

16.11.3 Static methods of Number

16.11.4 Methods of Number.prototype

(Number.prototype is where the methods of numbers are stored.)

16.11.5 Sources

  Quiz: advanced

See quiz app.