. With BigInt
s, you can safely store and operate on large integers even beyond the safe integer limit for Number
s.To create a BigInt
, add the n
suffix to any integer literal. For example, 123
becomes 123n
. The global BigInt(number)
function can be used to convert a Number
into a BigInt
. In other words, BigInt(123) === 123n
. Let’s use these two techniques to solve the problem we were having earlier:
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
Here’s another example, where we’re multiplying two Number
s:
1234567890123456789 * 123;
Looking at the least significant digits, 9
and 3
, we know that the result of the multiplication should end in 7
(because 9 * 3 === 27
). However, the result ends in a bunch of zeroes. That can’t be right! Let’s try again with BigInt
s instead:
1234567890123456789n * 123n;
This time we get the correct result.
The safe integer limits for Number
s don’t apply to BigInt
s. Therefore, with BigInt
we can perform correct integer arithmetic without having to worry about losing precision.
A new primitive #
BigInt
s are a new primitive in the JavaScript language. As such, they get their own type that can be detected using the typeof
operator:
typeof 123;
typeof 123n;
Because BigInt
s are a separate type, a BigInt
is never strictly equal to a Number
, e.g. 42n !== 42
. To compare a BigInt
to a Number
, convert one of them into the other’s type before doing the comparison or use abstract equality (==
):
42n === BigInt(42);
42n == 42;
When coerced into a boolean (which happens when using if
, &&
, ||
, or Boolean(int)
, for example), BigInt
s follow the same logic as Number
s.
if (0n) {
console.log('if');
} else {
console.log('else');
}
Operators #
BigInt
s support the most common operators. Binary +
, -
, *
, and **
all work as expected. /
and %
work, and round towards zero as needed. Bitwise operations |
, &
, <<
, >>
, and ^
perform bitwise arithmetic assuming a two’s complement representation for negative values, just like they do for Number
s.
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
Unary -
can be used to denote a negative BigInt
value, e.g. -42n
. Unary +
is not supported because it would break asm.js code which expects +x
to always produce either a Number
or an exception.
One gotcha is that it’s not allowed to mix operations between BigInt
s and Number
s. This is a good thing, because any implicit coercion could lose information. Consider this example:
BigInt(Number.MAX_SAFE_INTEGER) + 2.5;
What should the result be? There is no good answer here. BigInt
s can’t represent fractions, and Number
s can’t represent BigInt
s beyond the safe integer limit. For that reason, mixing operations between BigInt
s and Number
s results in a TypeError
exception.
The only exception to this rule are comparison operators such as ===
(as discussed earlier), <
, and >=
– because they return booleans, there is no risk of precision loss.
1 + 1n;
123 < 124n;
Because BigInt
s and Number
s generally don’t mix, please avoid overloading or magically “upgrading” your existing code to use BigInt
s instead of Number
s. Decide which of these two domains to operate in, and then stick to it. For new APIs that operate on potentially large integers, BigInt
is the best choice. Number
s still make sense for integer values that are known to be in the safe integer range.
Another thing to note is that >>> operator, which performs an unsigned right shift, does not make sense for BigInt
s since they’re always signed. For this reason, >>>
does not work for BigInt
s.
API #
Several new BigInt
-specific APIs are available.
The global BigInt
constructor is similar to the Number
constructor: it converts its argument into a BigInt
(as mentioned earlier). If the conversion fails, it throws a SyntaxError
or RangeError
exception.
BigInt(123);
BigInt(1.5);
BigInt('1.5');
The first of those examples passes a numeric literal to BigInt()
. This is a bad practice, since Number
s suffer from precision loss, and so we might already lose precision before the BigInt
conversion happens:
BigInt(123456789123456789);
For this reason, we recommend either sticking to the BigInt
literal notation (with the n
suffix), or passing a string (not a Number
!) to BigInt()
instead:
123456789123456789n;
BigInt('123456789123456789');
Two library functions enable wrapping BigInt
values as either signed or unsigned integers, limited to a specific number of bits. BigInt.asIntN(width, value)
wraps a BigInt
value to a width
-digit binary signed integer, and BigInt.asUintN(width, value)
wraps a BigInt
value to a width
-digit binary unsigned integer. If you’re doing 64-bit arithmetic for example, you can use these APIs to stay within the appropriate range:
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max);
→ 9223372036854775807n
BigInt.asIntN(64, max + 1n);
Note how overflow occurs as soon as we pass a BigInt
value exceeding the 64-bit integer range (i.e. 63 bits for the absolute numeric value + 1 bit for the sign).
BigInt
s make it possible to accurately represent 64-bit signed and unsigned integers, which are commonly used in other programming languages. Two new typed array flavors, BigInt64Array
and BigUint64Array
, make it easier to efficiently represent and operate on lists of such values:
const view = new BigInt64Array(4);
view.length;
view[0];
view[0] = 42n;
view[0];
The BigInt64Array
flavor ensures that its values remain within the signed 64-bit limit.
const max = 2n ** (64n - 1n) - 1n;
view[0] = max;
view[0];
view[0] = max + 1n;
view[0];
The BigUint64Array
flavor does the same using the unsigned 64-bit limit instead.
Polyfilling and transpiling BigInts #
At the time of writing, BigInt
s are only supported in Chrome. Other browsers are actively working on implementing them. But what if you want to use BigInt
functionality today without sacrificing browser compatibility? I’m glad you asked! The answer is… interesting, to say the least.
Unlike most other modern JavaScript features, BigInt
s cannot reasonably be transpiled down to ES5.
The BigInt
proposal changes the behavior of operators (like +
, >=
, etc.) to work on BigInt
s. These changes are impossible to polyfill directly, and they are also making it infeasible (in most cases) to transpile BigInt
code to fallback code using Babel or similar tools. The reason is that such a transpilation would have to replace every single operator in the program with a call to some function that performs type checks on its inputs, which would incur an unacceptable run-time performance penalty. In addition, it would greatly increase the file size of any transpiled bundle, negatively impacting download, parse, and compile times.
A more feasible and future-proof solution is to write your code using for now. JSBI is a JavaScript port of the BigInt
implementation in V8 and Chrome — by design, it behaves exactly like the native BigInt
functionality. The difference is that instead of relying on syntax, it exposes :import JSBI from './jsbi.mjs';
const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const two = JSBI.BigInt('2');
const result = JSBI.add(max, two);
console.log(result.toString());
Once BigInt
s are natively supported in all browsers you care about, you can babel-plugin-transform-jsbi-to-bigint to transpile your code to native BigInt
code and drop the JSBI dependency. For example, the above example transpiles to:
const max = BigInt(Number.MAX_SAFE_INTEGER);
const two = 2n;
const result = max + two;
console.log(result);
Further reading #
If you’re interested in how BigInt
s work behind the scenes (e.g. how they are represented in memory, and how operations on them are performed), read our V8 blog post with implementation details.
BigInt
support #