It seems like there is no maximum limit to a BigInt as per spec, which makes sense considering BigInts are supposed to be arbitrary-precision integers, whose "digits of precision are limited only by the available memory of the host system".

As for v8 specifically, according to this article on the v8 blog, the precision of BigInts are "arbitrary up to an implementation-defined limit". Unfortunately, I couldn't find any further information on how the limit is determined. Maybe someone else would be able to shed light on this based on these v8 BigInt implementation notes?

That said, based on the aforementioned articles, there doesn't seem to be a specific maximum value/size for a BigInt. Rather, it is likely determined based on the available memory on the system in some way.

Answer from zwliew on Stack Overflow
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Global_Objects › BigInt
BigInt - JavaScript | MDN
BigInt values represent integer values which are too high or too low to be represented by the number primitive.
🌐
W3Schools
w3schools.com › js › js_bigint.asp
JavaScript BigInt
JavaScript Numbers are only accurate up to 15 digits: // 15 digits: let x = 999999999999999; // 16 digits: let y = 9999999999999999; Try it Yourself » · All JavaScript Numbers are stored in a 64-bit floating-point format (IEEE 754 standard).
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Global_Objects › BigInt › asIntN
BigInt.asIntN() - JavaScript | MDN
25n = 00011001 (base 2) ^==== Use only the four remaining bits ===> 1001 (base 2) = -7n · Note: BigInt values are always encoded as two's complement in binary.
🌐
V8
v8.dev › features › bigint
BigInt: arbitrary-precision integers in JavaScript · V8
BigInts support the most common operators. Binary +, -, *, and ** all work as expected. / and % work, and round towards zero as needed. Bitwise operations |, &, &lt;<, >>, and ^ perform bitwise arithmetic assuming a two’s complement representation for negative values, just like they do for Numbers.
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Global_Objects › BigInt › asUintN
BigInt.asUintN() - JavaScript | MDN
The BigInt.asUintN() static method truncates a BigInt value to the given number of least significant bits and returns that value as an unsigned integer.
🌐
Exploring JS
exploringjs.com › impatient-js › ch_bigints.html
This web page has moved
“JavaScript for impatient programmers” is now “Exploring JavaScript”.
Top answer
1 of 4
9

Note: below measurements were taken in V8 (Chromium), should extend to other engines to confirm and post links to benchmarks.

Note 2: all these functions assume the argument is nonnegative, and may crash / loop / return invalid results otherwise.

simple version

return x.toString(2).length

as pointed out in the comments, even if it feels hacky to use toString(), it's unlikely there's an asymptotically faster way (because formatting a base 2 string is efficient and happens natively). it's O(n) with the number of bits of the bigint. other than that it's short and readable, so it's ideal for most uses.

better version

const i = (x.toString(16).length - 1) * 4
return i + 32 - Math.clz32(Number(x >> BigInt(i)))

this still uses the toString() approach, but it uses base 16 instead of base 2, meaning the temporary string that is produced is 4x shorter. formatting base 16 is also efficient, no real base change required, so still O(n).

the gains of using this are marginal for small bigints (+10% for 32 bits) but for big ones (such as messages or cryptography coefficients, meaning 512 / 1024 / 2048 / 4096) it's 2.5 to 4 times faster and puts less pressure on the GC as it only allocates a 2x long string. it's ideal for hot code and non-small integers.

"you may be overthinking this" version

there's fortunately a more performant way that does not involve toString() and instead relies on pre-allocated integers of different sizes that are successively compared to the target to get an upper bound for its size, in ceil(log2(n)) steps.

once the upper bound is established, the exact size is determined by bisection:

// find upper bound
let k = 0
while (true) {
    if (testersN === k) {
        testersCoeff.push(32 << testersN)
        testersBigCoeff.push(BigInt(testersCoeff[testersN]))
        testers.push(1n << testersBigCoeff[testersN])
        testersN++
    }
    if (x < testers[k]) break
    k++
}

if (!k)
    return 32 - Math.clz32(Number(x))

// determine length by bisection
k--
let i = testersCoeff[k]
let a = x >> testersBigCoeff[k]
while (k--) {
    let b = a >> testersBigCoeff[k]
    if (b)
        (i += testersCoeff[k], a = b)
}

return i + 32 - Math.clz32(Number(a))

this code uses the following global variables to store cached state:

const testersCoeff = []
const testersBigCoeff = []
const testers = []
let testersN = 0

in my tests, compared with the previous version, this is 2x faster for 512 bits, 7x faster for 4096 bits. it allocates at most 1x the size of the target integer, albeit in multiple integers.

ideal version

with proper native support, this would essentially be a O(1) operation (since the engine needs to know the length of the buffer used to store the bigint).

as for now, the best thing we can hope for is O(n) since there's no way to probe for the size of a bigint (i.e. ask if size is smaller or larger than a certain n) that does not involve an allocation proportional to n in the worst case.

2 of 4
5

The following code works incorrectly in some cases (pointed out in comments by @Matthijs), please see the safe (but slower) version in the end (benchmarks would be added later).

The following naïve version is about 3.5x faster than the currently most upvoted one for small bigints (tested on 960-bit):

const MAX = 2n ** 1023n;
function bitLengthNaive(n) {
    if (n === 0n) return 0;
    let v = n, len = 0;
    while (v >= MAX) {
        v >>= 1023n;
        len += 1023;
    }
    return len + Math.floor(Math.log2(Number(v))) + 1;
}

For bigints less than 1024 bits it can be inlined to n => Math.floor(Math.log2(Number(n))) + 1 (you see why it's called naïve). The 1023 constant comes from 2 ** 1023 being the largest power of two that can be represented as a finite Number (i.e. not +Infinity).

For 10240-bit bigints it's already 7x times slower (for 2048-bit 2x faster, and for 4096-bit 1.1x slower). Fortunately, we can just modify the original solution to get one that is 2x faster on 10240-bit and 3x faster on 960-bit (which is a bit slower than 3.5x, but in my opinion this is a good trade-off):

const testersCoeff = [];
const testersBigCoeff = [];
const testers = [];
let testersN = 0;

function bitLength(n) {
    if (n === 0n) return 0;

    // find upper bound
    let k = 0;
    while (true) {
        if (testersN === k) {
            testersCoeff.push(1023 << testersN);
            testersBigCoeff.push(BigInt(testersCoeff[testersN]));
            testers.push(1n << testersBigCoeff[testersN]);
            testersN++;
        }
        if (n < testers[k]) break;
        k++;
    }

    if (!k) return Math.floor(Math.log2(Number(n))) + 1;

    // determine length by bisection
    k--;
    let i = testersCoeff[k];
    let a = n >> testersBigCoeff[k];
    while (k--) {
        let b = a >> testersBigCoeff[k];
        if (b) {
            i += testersCoeff[k];
            a = b;
        }
    }

    return i + Math.floor(Math.log2(Number(a))) + 1;
}

UPD: for "ultrasmall" bigints (64-bit) naïve version and combined version are respectively 10% faster and 10% slower than the original solution.

Safe version (1023 replaced by 48):

const testersCoeff = [];
const testersBigCoeff = [];
const testers = [];
let testersN = 0;

function bitLength(n) {
    if (n === 0n) return 0;

    // find upper bound
    let k = 0;
    while (true) {
        if (testersN === k) {
            testersCoeff.push(48 << testersN);
            testersBigCoeff.push(BigInt(testersCoeff[testersN]));
            testers.push(1n << testersBigCoeff[testersN]);
            testersN++;
        }
        if (n < testers[k]) break;
        k++;
    }

    if (!k) return Math.floor(Math.log2(Number(n))) + 1;

    // determine length by bisection
    k--;
    let i = testersCoeff[k];
    let a = n >> testersBigCoeff[k];
    while (k--) {
        let b = a >> testersBigCoeff[k];
        if (b) {
            i += testersCoeff[k];
            a = b;
        }
    }

    return i + Math.floor(Math.log2(Number(a))) + 1;
}
Find elsewhere
🌐
JavaScript.info
javascript.info › tutorial › the javascript language › miscellaneous
BigInt
A bigint is created by appending n to the end of an integer literal or by calling the function BigInt that creates bigints from strings, numbers etc.
🌐
LogRocket
blog.logrocket.com › home › how to use javascript’s bigint
How to use JavaScript's BigInt - LogRocket Blog
June 4, 2024 - Hence, if we want to limit our calculations to 8-bit, 16-bit, 32-bit, or 64-bit arithmetic, we can use the asIntN method to achieve that by calling it completely with BigInt.asIntN(width, value).
🌐
Exploring JS
exploringjs.com › js › book › ch_bigints.html
Bigints – arbitrary-precision integers ES2020 (advanced) • Exploring JavaScript (ES2025 Edition)
Small integers are represented with fewer bits than large integers. There is no negative lower limit or positive upper limit for the integers that can be represented. A bigint literal is a sequence of one or more digits, suffixed with an n – for example: ... Bigints are primitive values. ...
🌐
GitHub
github.com › tc39 › proposal-bigint
GitHub - tc39/proposal-bigint: Arbitrary precision integers in JavaScript
There are a number of cases in JavaScript coding where integers larger than 253 come up — both instances where signed or unsigned 64-bit integers are needed and times where we may want integers even larger than 64-bits.
Starred by 562 users
Forked by 54 users
Languages   HTML 99.3% | Shell 0.7%
🌐
Medium
medium.com › @zandaqo › bitwise-operators-in-javascript-the-good-parts-a2e811a8c76b
Bitwise Operators in JavaScript: The Good Parts | by Maga D. Zandaqo | Medium
January 2, 2019 - Thus, we don’t have to “unpack” ... the BigInt proposal. Bitwise operations on BigInts return a BigInt that is not limited to 32 bits....
Top answer
1 of 6
39

Javascript represents all numbers as 64-bit double precision IEEE 754 floating point numbers (see the ECMAscript spec, section 8.5.) All positive integers up to 2^53 can be encoded precisely. Larger integers get their least significant bits clipped. This leaves the question of how can you even represent a 64-bit integer in Javascript -- the native number data type clearly can't precisely represent a 64-bit int.

The following illustrates this. Although javascript appears to be able to parse hexadecimal numbers representing 64-bit numbers, the underlying numeric representation does not hold 64 bits. Try the following in your browser:

<html>
  <head>
    <script language="javascript">
      function showPrecisionLimits() {
        document.getElementById("r50").innerHTML = 0x0004000000000001 - 0x0004000000000000;
        document.getElementById("r51").innerHTML = 0x0008000000000001 - 0x0008000000000000;
        document.getElementById("r52").innerHTML = 0x0010000000000001 - 0x0010000000000000;
        document.getElementById("r53").innerHTML = 0x0020000000000001 - 0x0020000000000000;
        document.getElementById("r54").innerHTML = 0x0040000000000001 - 0x0040000000000000;
      }
    </script>
  </head>
  <body onload="showPrecisionLimits()">
    <p>(2^50+1) - (2^50) = <span id="r50"></span></p>
    <p>(2^51+1) - (2^51) = <span id="r51"></span></p>
    <p>(2^52+1) - (2^52) = <span id="r52"></span></p>
    <p>(2^53+1) - (2^53) = <span id="r53"></span></p>
    <p>(2^54+1) - (2^54) = <span id="r54"></span></p>
  </body>
</html>

In Firefox, Chrome and IE I'm getting the following. If numbers were stored in their full 64-bit glory, the result should have been 1 for all the substractions. Instead, you can see how the difference between 2^53+1 and 2^53 is lost.

(2^50+1) - (2^50) = 1
(2^51+1) - (2^51) = 1
(2^52+1) - (2^52) = 1
(2^53+1) - (2^53) = 0
(2^54+1) - (2^54) = 0

So what can you do?

If you choose to represent a 64-bit integer as two 32-bit numbers, then applying a bitwise AND is as simple as applying 2 bitwise AND's, to the low and high 32-bit 'words'.

For example:

var a = [ 0x0000ffff, 0xffff0000 ];
var b = [ 0x00ffff00, 0x00ffff00 ];
var c = [ a[0] & b[0], a[1] & b[1] ];

document.body.innerHTML = c[0].toString(16) + ":" + c[1].toString(16);

gets you:

ff00:ff0000
2 of 6
19

Here is code for AND int64 numbers, you can replace AND with other bitwise operation

function and(v1, v2) {
    var hi = 0x80000000;
    var low = 0x7fffffff;
    var hi1 = ~~(v1 / hi);
    var hi2 = ~~(v2 / hi);
    var low1 = v1 & low;
    var low2 = v2 & low;
    var h = hi1 & hi2;
    var l = low1 & low2;
    return h*hi + l;
}
🌐
GitHub
gist.github.com › alexvictoor › fa518189c3534fce10ce5cfad3d07c8f
BigInt bitwise micro benchmark · GitHub
.add('Number bitwise', function() { input >> 8; }, { 'setup': function() { var input = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER ); } }) Number divide x 619,531,374 ops/sec ±1.02% (86 runs sampled) BigInt divide x 10,780,787 ops/sec ±1.88% (81 runs sampled) Number bitwise x 610,695,732 ops/sec ±1.12% (83 runs sampled) BigInt bitwise x 13,353,902 ops/sec ±1.12% (84 runs sampled) Fastest is Number divide,number bitwise
🌐
Sanori's Blog
sanori.github.io › 2024 › 01 › Handling-Large-Integers-in-JavaScript
Handling Large Integers in JavaScript | Sanori's Blog
January 16, 2024 - 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: If you want bitwise operations wider than 32 bits, you should use the ...
🌐
Medium
medium.com › @adeyemimuaz1 › mastering-bigint-in-javascript-handling-large-integers-with-ease-d6475647c519
Mastering BigInt in JavaScript: Handling Large Integers with Ease. | by Adeyemi Muaz Ayomide | Medium
April 26, 2024 - This article introduces BigInt and gives insights into managing precise and large-scale numerical operations in JavaScript applications. In Javascript, Numbers are represented internally as 64-bit floating-point values.
🌐
2ality
2ality.com › 2017 › 03 › es-integer.html
ES2020: BigInt – arbitrary precision integers
Given that the ECMAScript standard only has a single type for numbers (64-bit floating point numbers) it’s amazing how far JavaScript engines were able to go in their support for integers: fractionless numbers that are small enough are stored as integers (usually within 32 bits, possibly ...