BigInt, arbitrary precision integers in JavaScript

Published: September 03, 2018  •  javascript

The Chrome browser introduced with version 67 (end of May 2018) a new feature to the JavaScript language:
The numeric primitive type: BigInt

BigInt is a primitive that can represent integers with arbitrary precision, unlike the Number primitive that can only safely store integer values up to 2^53. BigInts allow an application to work with numbers that would not fit into a Number. An application never has to worry about overflow errors when it performs integer arithmetic with BigInt.

For example, with a BigInt, an application can represent the following number with 949 digits.

1981736304199322413033155801017007660571991838127382513183869502655132945955410381978047930657291929328288215
0606361136148620102904441975725292592628559967812484675646892511846918611620611557168971934110225307415064349
8363325492215252179814608914978848514521148286358193185975317403169331562554772751601328737118172997857019565
4412014954799861023018894168724656416695879451586197508165521397945043501171786040218486867988008752171425034
1196327155439333794638153206641651301969127462864035534874455812703397724619751741504170473595816399761522130
0466851042618062726302177752344216671603543132982051584234733113584510328373733271849956633295642753394057746
2921547661536249039107602105847940990310966177991999680511207851623398036926020603299101758654216200328207217
0802170818128446172076808512000167514374118804496410628196099710910975151856288299881083251368933286107688452
87219627992142295849162918727812831234961883883469929500772200319747415930309n

One of the application that depends on big number arithmetic is cryptography.
If you use a back end language that supports a 64-bit datatype like Java with long, BigInts allow a JavaScript application to safely represent these values on the client (for example primary keys, timestamps in nanoseconds).


Implementations and Standard

Chrome supports BigInt since version 67.

Apple is working on the BigInt support and it looks like it will be part of Safari 12.
https://developer.apple.com/safari/technology-preview/release-notes/

Mozilla works on an implementation too. You can track the progress here:
https://bugzilla.mozilla.org/show_bug.cgi?id=1366287

Also Microsoft is working on an implementation for their Edge browser:
https://github.com/Microsoft/ChakraCore/issues/5440


BigInt is at the time of writing this blog post (September 2018) not a ECMAScript standard. The proposal is in stage 3 and only proposals in stage 4 will be part of the next ECMAScript standard. One of the requirement for a proposal to move from 3 to 4 is that there must be two implementations by two independent VMs and they must pass the acceptance tests. With 3 browser implementations in the near future my guess is that there is a good chance that BigInt will be part of ECMAScript 2019 or 2020.

You can track all the JavaScript proposals in this GitHub repository: https://github.com/tc39/proposals

The just released version 7 of Babel supports BigInt and the upcoming 3.2 version of TypeScript introduces BigInts to TypeScript.


Syntax

To differentiate a BigInt from a Number you have to append the letter n to the end of the number. For example 156 is a Number, 156n is a BigInt.

You can convert a Number to a BigInt with the global BigInt(number) function.

const aBigInt = 11n;

const aNumber = 156;
const aBigInt = BigInt(aNumber);
aBigInt === 156n // true

BigInts can also be written with binary, octal or hexadecimal notation

const h = 0x100n;
// h === 256n

const o = 0o064n;
// o === 52n

const b = 0b10101010101n;
// b === 1365n

When used as a boolean, BigInt follows the same rule as a Number. 0n is falsy.

if (0n) {
  console.log('truthy');
} else {
  console.log('falsy');
}
// prints "falsy"

The typeof operator used with a BigInt returns "bigint"

typeof 156;
// "number"
typeof 156n;
// "bigint"

Operators

BigInts support the same operators that you can use with Numbers and work as expected.

20 - 3 * 4 ** 2 / (3 % 4) === 4
20n - 3n * 4n ** 2n / (3n % 4n) === 4n

/ and % rounds towards zero

6n / 7n === 0n
7n / 6n === 1n

Most bitwise operators also work like they do with Numbers

128 << 3
// 1024
128n << 3n
// 1024n

Not supported is the >>> operator, because BigInts are signed. To get an unsigned shift, pass in a positive BigInt to >>

128n >>> 3n
// Uncaught TypeError: BigInts have no unsigned right shift, use >> instead

The Unary - operator can be used for denoting a negative BigInt

const a = -156n

The unary + operator is not supported with BigInts, because it would cause a problem with asm.js which expects the unary + operator to always return a Number.

const b = +6
b === 6

const c = +6n
// Uncaught TypeError: Cannot convert a BigInt value to a number

There is one limitation you need to be aware of. You cannot mix Number and BigInt. The JavaScript engines throws an exception if you try to do that.

const d = 45 + 10n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

The JavaScript engine cannot implicit convert Number and BigInt because BigInt does not support fractions and Number can only represent integers safely up to 2^53.


Conversion to and from Number

You can always explicitly convert with the global functions BigInt() and Number()

BigInt(45) + 10n === 55n
45 + Number(10n) === 55

parseFloat() and parseInt() can also convert a BigInt to a Number.

const a = parseFloat(100n);
// a === 100

const b = parseInt(101n);
// b === 101

You need to be cautious with conversions to Number. If you convert a BigInt that is outside the safe integer range (2^53), Number(), parseFloat() and parseInt() do not throw an error, but the Number they return is not able to represent the integer correctly.
If the BigInt is greater than Number.MAX_VALUE these three methods return Infinity.

const bi = 2n**56n;
// bi === 72057594037927936n

const n = Number(72057594037927936n);
// n === 72057594037927940  


const large = 2n*10n**309n;
Number(large) === Infinity;
parseFloat(large) === Infinity;

Comparisons

As mentioned before you cannot mix Numbers and BigInts. There is one exception to this rule: comparison operators. The precision does not matter here because these operators resolve to a boolean value.

10 == 10n
// true

10 === 10n
// false

9 < 10n
// true

9n > 10
// false

Conversion to and from strings

The global BigInt() function cannot only convert Numbers to BigInts but also strings to BigInts

BigInt(10) === 10n;
BigInt('100') === 100n;

The function throws an exception if it cannot convert the provided value into a BigInt.

BigInt(23.5)
// RangeError: The number 23.5 cannot be converted to a BigInt because it is not an integer

BigInt('23.5')
// SyntaxError: Cannot convert 23.5 to a BigInt

BigInts also have a toString() method that convert the number to a string

const s = 100n.toString();
// s === "100"

The + operator can be used to concatenate strings and BigInts. Same behaviour that we already know when concatenating strings and Numbers.

const s1 = 'Amount: ' + 1;
// s1 === "Amount: 1"

const s2 = 'Amount: ' + 1n;
// s2 === "Amount: 1"

You can also use BigInts in template literals. Same behaviour that you already know from Numbers.

const value = 123n;
const s3 = `Amount: ${value}`
// s3 === "Amount: 123"

JSON

JSON does not support BigInt. If you try to convert a JavaScript object with a BigInt field to JSON the JavaScript VM throws an exception.

const obj = {name: 'test', value: 100n};
const j = JSON.stringify(obj);
// TypeError: Do not know how to serialize a BigInt

As a workaround you can convert BigInts to either Numbers (if they fit into the supported range) or to a string. JSON.stringify() supports a second parameter called a replacer, which we can use for writing a generic function that converts all BigInts to strings.

const j = JSON.stringify(obj, (key, value) => {
	if (typeof value === 'bigint') {
		return value.toString() + 'n';
	} else {
		return value;
	}
});

// j === "{"name":"test","value":"100n"}"

To parse such a JSON we can pass a function as second parameter (receiver) to the JSON.parse() method. This function is called for each key/value pair. It first checks if the value is a string, contains only digits and ends with the letter n. If true, it passes the string to the BigInt constructor else it returns the original value.

const obj2 = JSON.parse(j, (key, value) => {
  if (typeof value === 'string' && /^\d+n$/.test(value)) {
    return BigInt(value.slice(0, -1));
  }
  return value;
});

// obj2 --> {name: "test", value: 100n}

Math

For Numbers, the JavaScript VM provides a built-in Math object that has properties and methods for mathematical constants and functions. For BigInts there is no such object implemented. I guess a lot of the provided Math methods don't make sense for a number type without fraction, but there are a few methods that could be useful even for BigInts. Fortunately these are not that complicated to implement. Here a very trivial implementation of max, min, sign, abs and sqrt.

class BigIntMath {

	static max(...values) {
		if (values.length === 0) {
			return null;
		}

		if (values.length === 1) {
			return values[0];
		}

		let max = values[0];
		for (let i = 1; i < values.length; i++) {
			if (values[i] > max) {
				max = values[i];
			}
		}
		return max;
	}

	static min(...values) {
		if (values.length === 0) {
			return null;
		}

		if (values.length === 1) {
			return values[0];
		}

		let min = values[0];
		for (let i = 1; i < values.length; i++) {
			if (values[i] < min) {
				min = values[i];
			}
		}
		return min;
	}

	static sign(value) {
		if (value > 0) {
			return 1n;
		}
		if (value < 0) {
			return -1n;
		}
		return 0n;
	}

	static abs(value) {
		if (this.sign(value) === -1n) {
			return -value;
		}
	}

	static sqrt(value) {
		if (value < 0n) {
			throw 'square root of negative numbers is not supported'
		}

		if (value < 2n) {
			return value;
		}

		function newtonIteration(n, x0) {
			const x1 = ((n / x0) + x0) >> 1n;
			if (x0 === x1 || x0 === (x1 - 1n)) {
				return x0;
			}
			return newtonIteration(n, x1);
		}

		return newtonIteration(value, 1n);
	}

}

Usage:

const min = BigIntMath.min(1n,2n,3n,4n); // min == 1n
const max = BigIntMath.max(1n,2n,3n,4n); // min == 4n
const sign = BigIntMath.sign(-11n); // sign === -1n
const abs = BigIntMath.abs(-11n); // abs === 11n
const sqrt = BigIntMath.sqrt(81n); // sqrt === 9n

There is no need for a Math.pow() implementation, we can use the exponentiation operator introduced in ECMAScript 2016.

const a = Math.pow(3,4);
// a === 81

const b = 3n ** 4n;
// b === 81n

Polyfills

Because BigInt is a primitive there is no way for a polyfill to implement the n syntax and add BigInt support to the built-in operators in a browser that doesn't have a BigInt implementation yet or in an older browser that will never get BigInt support.

But there are Big Integer libraries that you can use today. The syntax looks different and is not that convenient as the built in implementation. The performance will also be slower than the native implementation, because all these libraries have to run their calculations in JavaScript.

Google published a chart on their BigInt blog post that compares the native implementation with a few Big Integer libraries: https://developers.google.com/web/updates/2018/05/bigint


The following example shows you how you can do big integers arithmetic with the bn.js library.

You add the library like any other JavaScript library

npm install bn.js

Then import it, create BN instances from strings or numbers and then use the provided methods.

import BN from 'bn.js';

const a = new BN('156');
const b = new BN(200);

const result = a.add(b);
// result.toString() === "356"


const b1 = new BN('90071992547409910000');
const b2 = new BN('29839283291982398238');
const r = b1.mul(b2);
// r.toString() === "2687683702295491619562538437047738580000"

The bn.js library provides a wide variety of methods. See the documentation on the GitHub project page: https://github.com/indutny/bn.js/

There are many other libraries that introduce big integer arithmetic to Node.js and the browser.