We now have native BigInts in JavaScript (not just 64-bit integers, but any arbitrary precision of integer with no pre-defined bit-width). Yay!

However, BigInts have a sordid past... and JavaScript has a sordid past, so the new BigInt primitives are shimmed in and both hold to old, unhelpful JS conventions as well as break some new conventions because, well, it's just how the cookie crumbled.

TL;DR:

For the impatient, this is what we're building up to:

bnToBuf(bn):

function bnToBuf(bn) {
  var hex = BigInt(bn).toString(16);
  if (hex.length % 2) { hex = '0' + hex; }

  var len = hex.length / 2;
  var u8 = new Uint8Array(len);

  var i = 0;
  var j = 0;
  while (i < len) {
    u8[i] = parseInt(hex.slice(j, j+2), 16);
    i += 1;
    j += 2;
  }

  return u8;
}

bufToBn(buf):

function bufToBn(buf) {
  var hex = [];
  u8 = Uint8Array.from(buf);

  u8.forEach(function (i) {
    var h = i.toString(16);
    if (h.length % 2) { h = '0' + h; }
    hex.push(h);
  });

  return BigInt('0x' + hex.join(''));
}

Usage:

bnToBuf('1234567890123456789012345678901234567890');
// Uint8Array [ 3, 160, 201, 32, 117, 192, 219, 243,
            184, 172, 188, 95, 150, 206, 63, 10, 210 ]

bufToBn([ 3, 160, 201, 32, 117, 192, 219, 243,
      184, 172, 188, 95, 150, 206, 63, 10, 210 ]);
// 1234567890123456789012345678901234567890n

And if you need to handle negative numbers, you'll need to take a look at How to Convert JS Signed BigInts to Hex

BigInt64Arrays are not exactly TypedArrays

When you're glancing over the almost non-existent documentation for BigInt you'll likely come across BigInt64Array and BigUint64Array, which are mostly unrelated.

As the name implies, these ints are not true BigInts, but rather capped at 64-bits wide.

However, they aren't just Numbers either.

Numbers can only survive normal bitwise operations up to 31-bits. With a little triple-shift trickery they can be coaxed into behaving well will a full 32 bits, but the full width 52-bit integers is not supported.

These BigInt64Array elements will work with many bitwise operations (though signed / negative numbers are still problematic for some operations).

Also, they're not compatible with other TypedArrays.

This snippet, for example, won't work:

var bns = BigInt64Array.from([ BigInt(1), BigInt(2), BigInt(3), BigInt(4) ]);
var u8 = Uint8Array.from(bns);

It throws an exception.

And despite being able to access it's ArrayBuffer you can't use that either:

var u8 = Uint8Array.from(bns.buffer);

However, it turns out that if you do it just right, you can get access to the bytes by calling new:

var u8 = new Uint8Array(bns.buffer);

Likewise you can ever-so-carefully convince it to go into a DataView as well:

var dv = new DataView(bns.buffer);

Here's the rub:

Even then you're capped to 64-bits. You can't even convert from a BigInt as you might suspect:

BigInt64Array.from(BigInt('12345678901234567890123456781234567783456')); // BigInt64Array []

BigInt64Array.from([ BigInt('12345678901234567890123456781234567783456') ]);
// BigInt64Array [ -4657930579192962016 ]

Converting from BigInts to Arrays/Buffers

So, ultimately, if you want to get a BigInt that's larger than 64-bits wide into indidivual bytes, you have to do it the old fashioned way - with hex (or binary strings):

function bnToBuf(bn) {
  // The handy-dandy `toString(base)` works!!
  var hex = BigInt(bn).toString(16);

  // But it still follows the old behavior of giving
  // invalid hex strings (due to missing padding),
  // but we can easily add that back
  if (hex.length % 2) { hex = '0' + hex; }

  // The byteLength will be half of the hex string length
  var len = hex.length / 2;
  var u8 = new Uint8Array(len);

  // And then we can iterate each element by one
  // and each hex segment by two
  var i = 0;
  var j = 0;
  while (i < len) {
    u8[i] = parseInt(hex.slice(j, j+2), 16);
    i += 1;
    j += 2;
  }

  // Tada!!
  return u8;
}

And thus we can handle integers of arbirary length in buffers, yay!

bnToBuf('1234567890123456789012345678901234567890')
// Uint8Array [ 3, 160, 201, 32, 117, 192, 219, 243,
            184, 172, 188, 95, 150, 206, 63, 10, 210 ]

Converting an ArrayBuffer to BigInt

By comparison, this is reverse is super simple.

function bufToBn(buf) {
  var hex = [];
  u8 = Uint8Array.from(buf);

  u8.forEach(function (i) {
    var h = i.toString(16);
    if (h.length % 2) { h = '0' + h; }
    hex.push(h);
  });

  return BigInt('0x' + hex.join(''));
}

There aren't any real gotchas - unless you're working with signed BigInts, in which case you'll want to read up on that specifically.

I cover negative number encoding and decoding in the artciles on converting between hex and decimal listed below:

Looking for more?

If you found this useful, and you're interested in solving related problems, take a look at these as well:

References


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )



Published

2018-12-13



Buy me a coffeeBuy me a coffee


  73% of Goal Reached


Want more like this?