Olaoluwa Mustapha Problem-solver and developer, building with simplicity in mind. I love open source, JavaScript, and anime.

A guide to using BigInt

4 min read 1245

JavaScript Logo

In the past, developers struggled with programs that made use of extremely large numbers because the JavaScript Number primitive had a limit on the minimum and maximum values it could represent correctly.

Consequently, this led to many unstable workarounds, such as converting large values to strings, or outsourcing work to third-party vendors, which led to bugs and/or large build sizes.

But with the introduction of the BigInt primitive to the ECMAScript specification, developers no longer need to rely on fragile workarounds or third-party libraries. Instead, BigInt allows them to safely work with numbers beyond the limits of the Number primitive.

In this article, we’ll learn what prompted the addition of the BigInt primitive into the ECMAScript specification, how BigInt solves the problem, and, lastly, we’ll learn how to get started with BigInt.

Why use BigInt?

Because there are many limitations using Number and data types in JavaScript.

In JS, the Number data type represents all numbers in JavaScript as double-precision-floating point number using the format defined by the IEEE 754, meaning that numbers in JavaScript are represented as double precision floats, or doubles for short.

Conventionally, because the Number primitive represents all numbers as doubles, they are always allocated 64 bits of memory. With it, numbers ranging from -1.710^308 to 1.710^308 can be represented and stored in variables.

Unfortunately, we cannot reliably work with all the numbers within this range as most of them are unsafe integers — numerical representations that reference more than one real world number.

This occurs because even though a specific real world number cannot be exactly represented according to the IEEE 754 format, it will be rounded using one of the standard “rounding modes” in order to force the number to adhere to the format.

The result? The computer will round certain numbers in a way that makes them equal to other numbers that do not need to be rounded in order to ensure it follows the format.

We made a custom demo for .
No really. Click here to check it out.

Essentially, these unsafe integers do not have their own private representation; instead, they erroneously share the representation of other real world numbers that do not need to undergo rounding to conform to the format.

Here’s an example:

// JS defines the maximum safe interger as a constant Number.MAX_SAFE_INTEGR
const safeInt = Number.MAX_SAFE_INTEGER // -> 9_007_199_254_740_991

// If we add one we get
safeInt + 1 // -> 9_007_199_254_740_992 ✅

// If we add 2...
safeInt + 2 // -> 9_007_199_254_740_992 🤦🏾‍♂️

// Therefore 9_007_199_254_740_992 or (2^53) is deemed unsafe because two real world numbers 9_007_199_254_740_992 and 9_007_199_254_740_993 are represented through it. That is why

safeInt + 1 === safeInt + 2 // -> true

So, what does this mean? Using numbers greater than or smaller than Number.MAX_SAFE_INTEGR or Number.MIN_SAFE_INTEGER is guaranteed to cause bugs.

Many of us may need not worry about this, as the range of numerical quantities we use is well within the limits of Number.MAX_SAFE_INTEGR and Number.MIN_SAFE_INTEGR.

Nevertheless, some developers have to work beyond these boundaries, such as those who work in finance or find themselves constantly performing calculations with incredibly large numbers.

Fortunately, there is a solution: BigInt.

What is BigInt?

BigInt is a relatively new numeric primitive/integer type in JavaScript. It was created to solve the limitations people ran into with the Number primitive and safe integer restrictions.

BigInt represents numbers with arbitrary precision, meaning it uses as much space as needed to store and represent large numbers instead of forcefully trying to represent them using a fixed amount of memory like the Number integer type does.

You can think of BigInt and Number like static and dynamic arrays. BigInt will use up more space if it needs to when representing a large number, like a dynamic array. But Number will only make use of the fixed memory initially allotted to it to represent numbers, like a static array.

BigInt gives us the ability to work with large numbers without having to worry about potentially losing precision (digits) or weird representation issues that hamper accuracy and create bugs.

Getting started with BigInt

To create a BigInt, simply add n at the end of any integer literal. Notice that doing so with decimals/floats will throw a RangeError:

// This is alright
const bigInteger = 1000000000000000000000000n

// This will throw a RangeError
const bigInteger = 1.5n // -> RangeError

// You can also create a BigInt with negative numbers
const negativeBigIntInteger = -1111111n // -> -1111111n

Alternatively, you can also use the global BigInt function passing an integer literal as an argument.

// This is also alright
const bigIntefer = BigInt(1000000000000000000000000000000000)

// This will still throw a RangeError
const bigInteger = BigInt(1.5)

BigInt literals can also be instantiated using strings, binary, hexadecimal, or octal notation.

// Strings
BigInt("1111111111111111111111111111111111111")
// -> 1111111111111111111111111111111111111n

// Binary
BigInt(0b100000000000000000000000000000000000000000000000000000000000000000000001111111)
// -> 151115727451828646838272n

// Hexadecimal
BigInt(0xfffffffffffffffffffffffffffffffffff9fff9fffffffffffffffff)
// -> 95780971304118053647396689196894323976171195136475136n

// Octal
BigInt(0o40000000000000000000000000000000000000000011112444)
// -> 713623846352979940529142984724747568191373312n

You cannot compare a BigInt and a regular Number using strict equality (===) because BigInt is a primitive on its own.

Therefore, calling typeof on a BigInt literal will return "bigint" instead of "number", causing strict comparisons between them to return false.

const a = 111111n
const b = 111111

a === b // -> false

However, if you were to use abstract equality (==), then comparing a BigInt literal with a value of 11n and a Number literal with a value of 11 will return true because both literals are of the same value.

const a = 11n
const b = 11

a == b // -> true

All arithmetic operations (+, -, /, *) can be performed on BigInt literals, with the exception of unary plus. For example, you can’t write +11n like you would +11.

On the other hand, you can increment BigInt literals with ++ and decrement them with --.

Moreover, arithmetic with BigInt literals must be between BigInt literals. A Number literal cannot be an operand in an arithmetic operation involving a BigInt. Trying to do so will result in a TypeError.

// We can do this
11n + 12n // -> 23n

// But we can't do this
11n + 12 // -> TypeError

Additionally, because BigInt arithmetic returns a BigInt, the return value will always be an integer of type "bigint".

5n / 3n // -> 1n

19n / 2n // -> 9n

BigInt literals greater than 0n are all coerced to true. While 0n, is coerced to false.

if (5n) {
        // Code block will run
}

if (0n) {
        // Code block will not run
}

Also, BigInt(true) will return 1n.

BigInt(true) === 1n // -> true

The BigInt global function contains two static methods that will restrict a BigInt representation to using a number of bits that is specified as the first parameter of both methods.

Once BigInt is within the specified space limit, it will be returned as either a signed or unsigned integer, depending on the method used.

The first method, BigInt.asIntN(bits, <bigInt-number>), returns the <bigInt-number> as a signed integer.

The second method, BigInt.asUintN(bits, <bigInt-number>) returns the <bigInt-number> as an unsigned integer.

These methods may be useful for explicit memory management. We know that, by default, BigInt uses as many bits as necessary to represent a number, but, if you are strapped for memory and know the range for the numerical values for your application, then these methods will be useful.

// For representing BigInt numbers as signed integers
BigInt.asIntN(bits, <BigInt>)

// For representing BigInt numbers as unsigned integers
BigInt.asUintN(bits, <BigInt>)

Conclusion

After reading this article, you hopefully have a deeper understanding of what BigInt is, the problems it solves, and how to use it.

Thank you for reading!

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Olaoluwa Mustapha Problem-solver and developer, building with simplicity in mind. I love open source, JavaScript, and anime.

Leave a Reply