JavaScript for impatient programmers (ES2022 edition)

## 18 Bigints – arbitrary-precision integers [ES2020] (advanced)

In this chapter, we take a look at bigints, JavaScript’s integers whose storage space grows and shrinks as needed.

### 18.1 Why bigints?

Before ECMAScript 2020, JavaScript handled integers as follows:

• There only was a single type for floating point numbers and integers: 64-bit floating point numbers (IEEE 754 double precision).

• Under the hood, most JavaScript engines transparently supported integers: If a number has no decimal digits and is within a certain range, it can internally be stored as a genuine integer. This representation is called small integer and usually fits into 32 bits. For example, the range of small integers on the 64-bit version of the V8 engine is from −231 to 231−1 (source).

• JavaScript numbers could also represent integers beyond the small integer range, as floating point numbers. Here, the safe range is plus/minus 53 bits. For more information on this topic, see §16.9.3 “Safe integers”.

Sometimes, we need more than signed 53 bits – for example:

• Twitter uses 64-bit integers as IDs for tweets (source). In JavaScript, these IDs had to be stored in strings.
• Financial technology uses so-called big integers (integers with arbitrary precision) to represent amounts of money. Internally, the amounts are multiplied so that the decimal numbers disappear. For example, USD amounts are multiplied by 100 so that the cents disappear.

### 18.2 Bigints

Bigint is a new primitive data type for integers. Bigints don’t have a fixed storage size in bits; their sizes adapt to the integers they represent:

• Small integers are represented with fewer bits than large integers.
• There is no negative lower limit or positive upper limit for the integers that can be represented.

A bigint literal is a sequence of one or more digits, suffixed with an `n` – for example:

``123n``

Operators such as `-` and `*` are overloaded and work with bigints:

``````> 123n * 456n
56088n``````

Bigints are primitive values. `typeof` returns a new result for them:

``````> typeof 123n
'bigint'``````

#### 18.2.1 Going beyond 53 bits for integers

JavaScript numbers are internally represented as a fraction multiplied by an exponent (see §16.8 “Background: floating point precision” for details). As a consequence, if we go beyond the highest safe integer 253−1, there are still some integers that can be represented, but with gaps between them:

``````> 2**53 - 2 // safe
9007199254740990
> 2**53 - 1 // safe
9007199254740991

> 2**53 // unsafe, same as next integer
9007199254740992
> 2**53 + 1
9007199254740992
> 2**53 + 2
9007199254740994
> 2**53 + 3
9007199254740996
> 2**53 + 4
9007199254740996
> 2**53 + 5
9007199254740996``````

Bigints enable us to go beyond 53 bits:

``````> 2n**53n
9007199254740992n
> 2n**53n + 1n
9007199254740993n
> 2n**53n + 2n
9007199254740994n``````

#### 18.2.2 Example: using bigints

This is what using bigints looks like (code based on an example in the proposal):

``````/**
* Takes a bigint as an argument and returns a bigint
*/
function nthPrime(nth) {
if (typeof nth !== 'bigint') {
throw new TypeError();
}
function isPrime(p) {
for (let i = 2n; i < p; i++) {
if (p % i === 0n) return false;
}
return true;
}
for (let i = 2n; ; i++) {
if (isPrime(i)) {
if (--nth === 0n) return i;
}
}
}

assert.deepEqual(
[1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
[2n, 3n, 5n, 7n, 11n]
);``````

### 18.3 Bigint literals

Like number literals, bigint literals support several bases:

• Decimal: `123n`
• Hexadecimal: `0xFFn`
• Binary: `0b1101n`
• Octal: `0o777n`

Negative bigints are produced by prefixing the unary minus operator: `-0123n`

#### 18.3.1 Underscores (`_`) as separators in bigint literals [ES2021]

Just like in number literals, we can use underscores (`_`) as separators in bigint literals:

``const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;``

Bigints are often used to represent money in the financial technical sector. Separators can help here, too:

``const priceInCents = 123_000_00n; // 123 thousand dollars``

As with number literals, two restrictions apply:

• We can only put an underscore between two digits.
• We can use at most one underscore in a row.

With most operators, we are not allowed to mix bigints and numbers. If we do, exceptions are thrown:

``````> 2n + 1
TypeError: Cannot mix BigInt and other types, use explicit conversions``````

The reason for this rule is that there is no general way of coercing a number and a bigint to a common type: numbers can’t represent bigints beyond 53 bits, bigints can’t represent fractions. Therefore, the exceptions warn us about typos that may lead to unexpected results.

For example, should the result of the following expression be `9007199254740993n` or `9007199254740992`?

``2**53 + 1n``

It is also not clear what the result of the following expression should be:

``2n**53n * 3.3``

#### 18.4.1 Arithmetic operators

Binary `+`, binary `-`, `*`, `**` work as expected:

``````> 7n * 3n
21n``````

It is OK to mix bigints and strings:

``````> 6n + ' apples'
'6 apples'``````

`/`, `%` round towards zero (like `Math.trunc()`):

``````> 1n / 2n
0n``````

Unary `-` works as expected:

``````> -(-64n)
64n``````

Unary `+` is not supported for bigints because much code relies on it coercing its operand to number:

``````> +23n
TypeError: Cannot convert a BigInt value to a number``````

#### 18.4.2 Ordering operators

Ordering operators `<`, `>`, `>=`, `<=` work as expected:

``````> 17n <= 17n
true
> 3n > -1n
true``````

Comparing bigints and numbers does not pose any risks. Therefore, we can mix bigints and numbers:

``````> 3n > -1
true``````

#### 18.4.3 Bitwise operators

##### 18.4.3.1 Bitwise operators for numbers

Bitwise operators interpret numbers as 32-bit integers. These integers are either unsigned or signed. If they are signed, the negative of an integer is its two’s complement (adding an integer to its two’s complement – while ignoring overflow – produces zero):

``````> 2**32-1 >> 0
-1``````

Due to these integers having a fixed size, their highest bits indicate their signs:

``````> 2**31 >> 0 // highest bit is 1
-2147483648
> 2**31 - 1 >> 0 // highest bit is 0
2147483647``````
##### 18.4.3.2 Bitwise operators for bigints

For bigints, bitwise operators interpret a negative sign as an infinite two’s complement – for example:

• `-1` is `···111111` (ones extend infinitely to the left)
• `-2` is `···111110`
• `-3` is `···111101`
• `-4` is `···111100`

That is, a negative sign is more of an external flag and not represented as an actual bit.

##### 18.4.3.3 Bitwise Not (`~`)

Bitwise Not (`~`) inverts all bits:

``````> ~0b10n
-3n
> ~0n
-1n
> ~-2n
1n``````
##### 18.4.3.4 Binary bitwise operators (`&`, `|`, `^`)

Applying binary bitwise operators to bigints works analogously to applying them to numbers:

``````> (0b1010n |  0b0111n).toString(2)
'1111'
> (0b1010n &  0b0111n).toString(2)
'10'

> (0b1010n | -1n).toString(2)
'-1'
> (0b1010n & -1n).toString(2)
'1010'``````
##### 18.4.3.5 Bitwise signed shift operators (`<<` and `>>`)

The signed shift operators for bigints preserve the sign of a number:

``````> 2n << 1n
4n
> -2n << 1n
-4n

> 2n >> 1n
1n
> -2n >> 1n
-1n``````

Recall that `-1n` is a sequence of ones that extends infinitely to the left. That’s why shifting it left doesn’t change it:

``````> -1n >> 20n
-1n``````
##### 18.4.3.6 Bitwise unsigned right shift operator (`>>>`)

There is no unsigned right shift operator for bigints:

``````> 2n >>> 1n
TypeError: BigInts have no unsigned right shift, use >> instead``````

Why? The idea behind unsigned right shifting is that a zero is shifted in “from the left”. In other words, the assumption is that there is a finite amount of binary digits.

However, with bigints, there is no “left”, their binary digits extend infinitely. This is especially important with negative numbers.

Signed right shift works even with an infinite number of digits because the highest digit is preserved. Therefore, it can be adapted to bigints.

#### 18.4.4 Loose equality (`==`) and inequality (`!=`)

Loose equality (`==`) and inequality (`!=`) coerce values:

``````> 0n == false
true
> 1n == true
true

> 123n == 123
true

> 123n == '123'
true``````

#### 18.4.5 Strict equality (`===`) and inequality (`!==`)

Strict equality (`===`) and inequality (`!==`) only consider values to be equal if they have the same type:

``````> 123n === 123
false
> 123n === 123n
true``````

### 18.5 The wrapper constructor `BigInt`

Analogously to numbers, bigints have the associated wrapper constructor `BigInt`.

#### 18.5.1 `BigInt` as a constructor and as a function

• `new BigInt()`: throws a `TypeError`.

• `BigInt(x)` converts arbitrary values `x` to bigint. This works similarly to `Number()`, with several differences which are summarized in tbl. 13 and explained in more detail in the following subsections.

Table 13: Converting values to bigints.
`x` `BigInt(x)`
`undefined` Throws `TypeError`
`null` Throws `TypeError`
boolean `false` `→` `0n`, `true` `→` `1n`
number Example: `123` `→` `123n`
Non-integer `→` throws `RangeError`
bigint `x` (no change)
string Example: `'123'` `→` `123n`
Unparsable `→` throws `SyntaxError`
symbol Throws `TypeError`
object Configurable (e.g. via `.valueOf()`)
##### 18.5.1.1 Converting `undefined` and `null`

A `TypeError` is thrown if `x` is either `undefined` or `null`:

``````> BigInt(undefined)
TypeError: Cannot convert undefined to a BigInt
> BigInt(null)
TypeError: Cannot convert null to a BigInt``````
##### 18.5.1.2 Converting strings

If a string does not represent an integer, `BigInt()` throws a `SyntaxError` (whereas `Number()` returns the error value `NaN`):

``````> BigInt('abc')
SyntaxError: Cannot convert abc to a BigInt``````

The suffix `'n'` is not allowed:

``````> BigInt('123n')
SyntaxError: Cannot convert 123n to a BigInt``````

All bases of bigint literals are allowed:

``````> BigInt('123')
123n
> BigInt('0xFF')
255n
> BigInt('0b1101')
13n
> BigInt('0o777')
511n``````
##### 18.5.1.3 Non-integer numbers produce exceptions
``````> BigInt(123.45)
RangeError: The number 123.45 cannot be converted to a BigInt because
it is not an integer
> BigInt(123)
123n``````
##### 18.5.1.4 Converting objects

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

``````> BigInt({valueOf() {return 123n}})
123n``````

#### 18.5.2 `BigInt.prototype.*` methods

`BigInt.prototype` holds the methods “inherited” by primitive bigints:

• `BigInt.prototype.toLocaleString(locales?, options?)`
• `BigInt.prototype.toString(radix?)`
• `BigInt.prototype.valueOf()`

#### 18.5.3 `BigInt.*` methods

• `BigInt.asIntN(width, theInt)`
Casts `theInt` to `width` bits (signed). This influences how the value is represented internally.

• `BigInt.asUintN(width, theInt)`
Casts `theInt` to `width` bits (unsigned).

#### 18.5.4 Casting and 64-bit integers

Casting allows us to create integer values with a specific number of bits. If we want to restrict ourselves to just 64-bit integers, we have to always cast:

``````const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);``````

### 18.6 Coercing bigints to other primitive types

This table show what happens if we convert bigints to other primitive types:

Convert to Explicit conversion Coercion (implicit conversion)
boolean `Boolean(0n)` `→` `false` `!0n` `→` `true`
`Boolean(int)` `→` `true` `!int` `→` `false`
number `Number(7n)` `→` `7` (example) `+int` `→` `TypeError` (1)
string `String(7n)` `→` `'7'` (example) `''+7n` `→` `'7'` (example)

Footnote:

• (1) Unary `+` is not supported for bigints, because much code relies on it coercing its operand to number.

### 18.7 TypedArrays and DataView operations for 64-bit values

Thanks to bigints, Typed Arrays and DataViews can support 64-bit values:

• Typed Array constructors:
• `BigInt64Array`
• `BigUint64Array`
• DataView methods:
• `DataView.prototype.getBigInt64()`
• `DataView.prototype.setBigInt64()`
• `DataView.prototype.getBigUint64()`
• `DataView.prototype.setBigUint64()`

### 18.8 Bigints and JSON

The JSON standard is fixed and won’t change. The upside is that old JSON parsing code will never be outdated. The downside is that JSON can’t be extended to contain bigints.

Stringifying bigints throws exceptions:

``````> JSON.stringify(123n)
TypeError: Do not know how to serialize a BigInt
> JSON.stringify([123n])
TypeError: Do not know how to serialize a BigInt``````

#### 18.8.1 Stringifying bigints

Therefore, our best option is to store bigints in strings:

``````const bigintPrefix = '[[bigint]]';

function bigintReplacer(_key, value) {
if (typeof value === 'bigint') {
return bigintPrefix + value;
}
return value;
}

const data = { value: 9007199254740993n };
assert.equal(
JSON.stringify(data, bigintReplacer),
'{"value":"[[bigint]]9007199254740993"}'
);``````

#### 18.8.2 Parsing bigints

The following code shows how to parse strings such as the one that we have produced in the previous example.

``````function bigintReviver(_key, value) {
if (typeof value === 'string' && value.startsWith(bigintPrefix)) {
return BigInt(value.slice(bigintPrefix.length));
}
return value;
}

const str = '{"value":"[[bigint]]9007199254740993"}';
assert.deepEqual(
JSON.parse(str, bigintReviver),
{ value: 9007199254740993n }
);``````

### 18.9 FAQ: Bigints

#### 18.9.1 How do I decide when to use numbers and when to use bigints?

My recommendations:

• Use numbers for up to 53 bits and for Array indices. Rationale: They already appear everywhere and are handled efficiently by most engines (especially if they fit into 31 bits). Appearances include:
• `Array.prototype.forEach()`
• `Array.prototype.entries()`
• Use bigints for large numeric values: If your fraction-less values don’t fit into 53 bits, you have no choice but to move to bigints.

All existing web APIs return and accept only numbers and will only upgrade to bigint on a case-by-case basis.

#### 18.9.2 Why not just increase the precision of numbers in the same manner as is done for bigints?

One could conceivably split `number` into `integer` and `double`, but that would add many new complexities to the language (several integer-only operators etc.). I’ve sketched the consequences in a Gist.

Acknowledgements:

• Thanks to Daniel Ehrenberg for reviewing an earlier version of this content.
• Thanks to Dan Callahan for reviewing an earlier version of this content.