Categories
JavaScript JavaScript Basics

Using JavaScript BigInt to Represent Large Numbers

Spread the love

To represent integers larger than 2**53 – 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

It can be constructed from numbers and hexadecimal or binary strings. Also, it supports bitwise operations like AND, OR, NOT, and XOR. The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed.

Also, the unary + operator isn’t supported to not break asm.js. These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

Also, when converting between number and BigInt and vice versa, we have to be careful because the precision of BigInt might be lost when a BigInt is converted into a number.


Usage

To define a BigInt, we can write the following if we want to pass in a whole number:

const bigInt = BigInt(1);  
console.log(bigInt);

It would log 1n when we run the console.log statement. If we want to pass a string into the factory function instead, we can write:

const bigInt = BigInt('2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222');  
console.log(bigInt);

We can also pass an hexadecimal number string with a string that starts with 0x into the factory function:

const bigHex = BigInt("0x1fffffffffffff111111111");  
console.log(bigHex);

The code above would log 618970019642690073311383825n when we run console.log on bigHex.

Likewise, we can pass in a binary string with a string that starts with 0b and binary string in the remainder of the string to get a BigInt:

const bigBin = BigInt("0b111111111111111000000000011111111111111111111111");  
console.log(bigBin);

The code above would get us 281466395164671n when we run console.log on it. Passing in strings would be handy if the BigInt that we want to create is outside of the range that can be accepted by the number type.

We can also define a BigInt with a BigInt literal, we can just attach an n character to the whole number. For example, we can write:

const bigInt = 22222222222222222222222222222222n;  
console.log(bigInt);

Then, we get 22222222222222222222222222222222n as the value of bigInt when we log the value.

BigInt has its own data type in JavaScript. When you run the typeof operator on a BigInt variable, constant or value, we would get bigint. For example, when we run:

typeof 2n === 'bigint';  
typeof BigInt(2) === 'bigint';

Both lines of code would evaluate to true. However, if we wrap it with the Object factory function, we get that the type is an object:

typeof Object(2n) === 'object';

The code above would evaluate to true.

We can apply number operations to BigInts.

This includes the arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation. Also, it supports bitwise operations like AND, OR, NOT, and XOR.

The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed. Also, the unary + operator isn’t supported to not break asm.js.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers. For example, if we have:

const bigInt = BigInt(Number.MAX_SAFE_INTEGER);  
console.log(bigInt);
const biggerInt = bigInt + BigInt(1);  
console.log(biggerInt);
const evenBiggerInt = bigInt + BigInt(2);  
console.log(evenBiggerInt);
const multi = bigInt * BigInt(2);  
console.log(multi);
const subtr = bigInt - BigInt(10);  
console.log(subtr);
const remainder = bigInt % BigInt(1);  
console.log(remainder);
const bigN = bigInt ** BigInt(54);  
console.log(bigN);
const veryNegativeNum = bigN * -BigInt(1)  
console.log(veryNegativeNum);

We get 9007199254740991n for bigInt, 9007199254740992n for biggerInt, 9007199254740993n for evenBiggerInt, 18014398509481982n for multi, 9007199254740981n for subtr, 0n for remainder, 3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for bigN, and -3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for veryNegativeNum.

Note that when we get fractional results, the fractional parts will be truncated when result has it. BigInt is a big integer and it’s not for storing decimals. For example, in the examples below:

const expected = 8n / 2n;  
console.log(expected)const rounded = 9n / 2n;  
console.log(rounded)

We get 4n for expected and rounded. This is because the fractional part is removed from the BigInt.

Comparison operators can be applied to BigInts. The operands can be either BigInt or numbers. We can use the bigger than, bigger than or equal to, less than, less than or equal to, double equals, and triple equals operators with BigInts.

For example, we can compare 1n to 1:

1n === 1

The code above would evaluate to false because BigInt and numbers aren’t the same types. But when we replace triple equals with double equals, like in the code below:

1n == 1

The statement above evaluates to true because only the value is compared.

Note that in both examples, we mixed BigInt operands with number operands. This is allowed for comparison operators.

BigInts and numbers can be compared together with other operators as well, like in the following examples:

1n < 9  
// true9n > 1  
// true9 > 9n  
// false  
  
9n > 9  
// false  
  
9n >= 9  
// true

Also, they can be mixed together in one array and sorted together. For example, if we have the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort();  
console.log(mixedNums)

We get [-120n, 0, 0n, 12, 24, 5n, 6]. We also sort it in descending order with the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort((a, b) => {  
  if (a > b) {  
    return -1  
  } else if (a < b) {  
    return 1  
  }  
  return 0  
});  
console.log(mixedNums)

In the code above, we used the comparison operators to compare the value of the numbers and return -1 if the first number is bigger than the second number, return 1 if the first number is less than the second number, and return 0 otherwise.

If we wrap BigInts with Object, then they’re compared as objects, so two objects are only considered the same if the same instance is referenced. For example, if we have:

0n === Object(0n);  
Object(0n) === Object(0n);  
const o = Object(0n);  
o === o;

Then the first three lines in the code above would evaluate to false while the last line would evaluate to true.

When BigInts are coerced into booleans, they act the same as if they’re numbers. For example, Boolean(0n) would return false, and anything else would return true.

For example, we can coerce them into booleans like in the following code:

0n || 11n  
// 11n  
  
0n && 11n  
// 0n  
  
Boolean(0n)  
// false  
  
Boolean(11n)  
// true  
  
!11n  
// false  
  
!0n  
// true

BigInt Methods

The BigInt object has several methods. There are the static asIntN() and asUintN() methods, and the toLocaleString(), toString(), and valueOf() instance methods.

The asIntN method wraps a BigInt value between -2 to the width minus 1 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** (62n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

Then we get the literal of the actual value of bigNum modulo 62 returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** (63n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

We get -1n since the 2n ** (63n — 1n) modulo 2n ** 63n is returned.

The asUintN() method wraps a BigInt value between 0 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

Then we get the literal of the actual value of bigNum returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

We get 2047n since the 2n ** 11n — 1n modulo 2n ** 11n is returned.

BigInt has a toLocaleString() method to return the value of the string for the BigInt depending on the locale we pass in. For example, if we want to get the French representation of a BigInt, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toLocaleString('fr'));

The code above will log 1 152 921 504 606 846 975.

The toString() method will convert a BigInt to a string. For example, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toString());

The code above will log 1152921504606846975.

The valueOf method will get the value of the BigInt object. For example, if we run:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.valueOf());

Then we get 1152921504606846975n logged.

BigInts aren’t supported by JSON, so a TypeError would be raised if we try to convert it to JSON with JSON.stringify().

However, we can convert it to something supported like a string, then it can be stored as JSON. We can override the toJSON method of a BigInt by writing:

BigInt.prototype.toJSON = function() {  
  return this.toString();  
}

const bigIntString = JSON.stringify(88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888n)  
console.log(bigIntString);

Then, we get: “88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888”.

BigInts are useful for representing integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

BigInts support most bitwise operations like AND, OR, NOT, and XOR.

They can also be converted from hexadecimal or binary numbers.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *