JavaScript typed arrays: 64-bit integers?

ECMAScript 2020 now has a built-in BigInt type, with BigInt64Array and BigUint64Array typed arrays. The internal 64-bit representations are converted to and from BigInt values, which are needed to keep the full precision.

BigInt and the array types are still relatively new, so see below if you need to support older browsers or Node versions. You can use resources like CanIUse.com to see what browsers to help you decide if it's an option or not. Polyfills could also be an option as a workaround until you phase out support for unsupported browsers.


Answer for older browsers/Node environments:

There's no practical way to implement an Int64Array, because all numbers in JavaScript are 64-bit floating point numbers, which only have 53 bits of precision. Like Simeon said in his comment, you could use a big integer library, but it would be much slower.

If you really need an array of 64-bit integers, regardless of performance, the Google Closure library has a 64-bit Long class that I would imagine is faster than a more general big integer library. I've never used it though, and I don't know if you can separate it easily from the rest of the library.


You can safely read a number below 2^53-1 (aka 0x1fffffffffffff or 9007199254740991), but not above that. Below is the code for doing this.

As said you cannot safely go beyond the 2^53-1 integer with Javascript numbers, because in Javascript numbers are really always represented as double precision 64 bit floating point numbers. These represent numbers as a base and a exponent within these 64 bits, with 53 bits for the base and the rest being for the exponent, so you'll lose precise integer precision when you go beyond 53 bits and need to use the exponent.

But here's at least how you can check for whether an unsigned 64 bit long integer in a Uint8Array is less than 2^53-1 and then safely read it if you want:

function getUint64(inputArray, index, littleEndian) {
  const dataView = new DataView(inputArray.buffer);
  let hi = dataView.getUint32(index, littleEndian);
  let lo = dataView.getUint32(index + 4, littleEndian);
  if (littleEndian) {
    const tmp = hi;
    hi = lo;
    lo = tmp;
  }
  if (hi > 0x1fffff) {
    throw new Error(
      'Cannot safely parse uints over 2^53 - 1 (0x1fffffffffffff) in to a 64 bit float.'
    );
  }
  const numberValue = (hi * 0x100000000) + lo;
  return numberValue;
}

// Tests gotten from this other excellent answer here: https://stackoverflow.com/a/53107482/628418
// [byteArray, littleEndian, expectedValue, expectError]
const testValues = [
  // big-endian
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0xff]),  false, 255], 
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0xff, 0xff]),  false, 65535],
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0xff, 0xff, 0xff, 0xff]),  false, 4294967295],
  [new Uint8Array([0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00]),  false, 4294967296],
  [new Uint8Array([0x00, 0x1f, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff]),  false, 9007199254740991], // maximum precision
  [new Uint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, 9007199254740992, true], // precision lost
  [new Uint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x01]),  false, 9007199254740992, true], // precision lost

  // little-endian
  [new Uint8Array([0xff, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 255], 
  [new Uint8Array([0xff, 0xff, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 65535],
  [new Uint8Array([0xff, 0xff, 0xff, 0xff,  0x00, 0x00, 0x00, 0x00]),  true, 4294967295],
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x01, 0x00, 0x00, 0x00]),  true, 4294967296],
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x01, 0x00, 0x00]),  true, 1099511627776],
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x01, 0x00]),  true, 281474976710656],
  [new Uint8Array([0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0x1f, 0x00]),  true, 9007199254740991], // maximum precision
];

testValues.forEach(testGetUint64);

function testGetUint64([bytes, littleEndian, expectedValue, expectError]) {
  if (expectError) {
    try {
      const val = getUint64(bytes, 0, littleEndian);
      console.error('did not get the expected error');
    } catch(error) {
      console.log('got expected error: ' + error.message);
    }
  } else {
    const val = getUint64(bytes, 0, littleEndian);
    console.log(val === expectedValue? 'pass' : 'FAIL. expected '+expectedValue+', received '+val);
  }
}