Handling Large Integers in JavaScript

Why we should care: JavaScript’s unique Number type

Handling large integers in JavaScript requires care. JavaScript treats numeric variables as a single type called Number. Unlike other programming languages, JavaScript does not divide numbers into real and integer types such as float and int or storage sizes such as short, int, and long. JavaScript’s Number type seems to use the IEEE-754 64-bit floating point number representation. This property is very convenient for most casual calculations. However, handling large integers requires slightly different care than other programming languages. Especially in environments like competitive programming or coding tests, you may fail.

How big is a large integer?

Bitwise operations: 32 bits

As explained in the previous article, JavaScript treats numbers as 32-bit integers when performing bitwise operations. Therefore, we can observe a strange phenomenon in which the following equation is true without errors:

1
1 << 32 === 1

If you want bitwise operations wider than 32 bits, you should use the BigInt type.

Numeric operations: 53 bits

JavaScript does not distinguish between real numbers and integers, but precision decreases when numbers grow larger than a certain level. To be more precise, if the calculation result requires more than 53 bits when expressed as an integer, we should not treat the result as an integer. For example, the following expression is true in JavaScript:

1
9007199254740992 + 1 === 9007199254740992

Therefore, if we want exact integer arithmetic, we must check whether the result is between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER. If we wish exact integer arithmetic beyond that range, we should consider the BigInt type.

Limitations of BigInt

ECMAScript 2020 introduced the BigInt type for arbitrary precision integer calculations. It solved the problem of handling large integers but also has limitations.

Incompatible with Number type

The BigInt type is incompatible with the Number type. For example, 1n + 1 will raise a TypeError. Therefore, we need to check the types of both operands of binary operators and cast one of them if needed. You also cannot use Math.* functions since Math.* functions are defined for the Number types. You can’t use JSON.stringify either.

BigInt operations take too long time sometimes

The current implementation of BigInt operations is in a best-effort manner without any restrictions. In theory, it has the advantage of being able to compute any large integer. However, in reality, if the result is too large, it may cause the JavaScript engine to stop. You can experience your JavaScript engine hanging with the following simple command:

1
2
s = BigInt(1e9 + 7)
(s ** s) % s

Therefore, unless you have to use the BigInt type, it is better to use the Number type.

Pitfalls of integer handling in JS

Migrating bit-masking code from other programming languages

In the case of C/C++ or Java, bitwise operation assumes 64-bit. Python allows bitwise operations even on large numbers of bits. However, the bitwise operation of the JavaScript’s Number type is limited to 32 bits. So, we should refactor the implementation to the BigInt type if the number of bits to be processed exceeds 32.

Modular arithmetic including multiplication

In competitive programming, some problems require modular arithmetic. In most cases, the base decimal is at most 32 bits. Therefore, there is no problem if we implement modulo arithmetic operations with the Number type. However, if the algorithm requires multiplication, you must consider whether the multiplication result exceeds 53 bits. For example, 10^9 + 7, a widely used prime number as modulo base, requires 30 bits. The multiplication of two numbers can be 60 bits, more than 53 bits. Therefore, we should consider the BigInt type to implement multiplication.