Skip to content Skip to sidebar Skip to footer

How To Read 64-bit Integer From An Arraybuffer / Dataview In Javascript

Given a 64-bit (8-byte) little-endian ArrayBuffer of bytes, how can we read 64-bit integer values in JavaScript? I experimented and came up with this, but is there a more elegant s

Solution 1:

Based on the original experiment and Sebastian Speitel's suggestion/fix, this function returns a 64-bit value until precision is lost after Number.MAX_SAFE_INTEGER

DataView.prototype.getUint64 = function(byteOffset, littleEndian) {
  // split 64-bit number into two 32-bit partsconst left =  this.getUint32(byteOffset, littleEndian);
  const right = this.getUint32(byteOffset+4, littleEndian);

  // combine the two 32-bit valuesconst combined = littleEndian? left + 2**32*right : 2**32*left + right;

  if (!Number.isSafeInteger(combined))
    console.warn(combined, 'exceeds MAX_SAFE_INTEGER. Precision may be lost');

  return combined;
}

Tested below:

// [byteArray, littleEndian, expectedValue]const testValues = [
  // big-endian
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0xff]),  false, 255], 
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0xff, 0xff]),  false, 65535],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0xff, 0xff, 0xff, 0xff]),  false, 4294967295],
  [newUint8Array([0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00]),  false, 4294967296],
  [newUint8Array([0x00, 0x1f, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff]),  false, 9007199254740991], // maximum precision
  [newUint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, 9007199254740992], // precision lost
  [newUint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x01]),  false, 9007199254740992], // precision lost// little-endian
  [newUint8Array([0xff, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 255], 
  [newUint8Array([0xff, 0xff, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 65535],
  [newUint8Array([0xff, 0xff, 0xff, 0xff,  0x00, 0x00, 0x00, 0x00]),  true, 4294967295],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x01, 0x00, 0x00, 0x00]),  true, 4294967296],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x01, 0x00, 0x00]),  true, 1099511627776],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x01, 0x00]),  true, 281474976710656],
  [newUint8Array([0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0x1f, 0x00]),  true, 9007199254740991], // maximum precision
];

testValues.forEach(testGetUint64);

functiontestGetUint64([bytes, littleEndian, expectedValue]) {
  const val = newDataView(bytes.buffer).getUint64(0, littleEndian);
  console.log(val === expectedValue? 'pass' : 'FAIL. expected '+expectedValue+', received '+val);
}

Solution 2:

Some browsers are starting to support the experimental BigInt global object:

BigInt is a built-in object that provides a way to represent whole numbers larger than 2, which is the largest number JavaScript can reliably represent with the Number primitive.

If you are only targeting these browsers then you can use this to get larger values than can be supported by a Number. Additionally, Chrome currently supports the DataView.getBigInt64( position, littleEndian ) and DataView.getBigUint64( position, littleEndian ) functions that return BigInt values.

Read 64-bit unsigned values

functiongetBigUint64( view, position, littleEndian = false)
{
  if ( "getBigUint64"inDataView.prototype )
  {
    return view.getBigUint64( position, littleEndian );
  }
  else
  {
    const lsb = BigInt( view.getUint32( position + (littleEndian ? 0 : 4), littleEndian ) );
    const gsb = BigInt( view.getUint32( position + (littleEndian ? 4 : 0), littleEndian ) );
    return lsb + 4294967296n * gsb;
  }
}

Read 64-bit signed values:

function getBigInt64( view, position, littleEndian = false)
{
  if ( "getBigInt64"in DataView.prototype )
  {
    return view.getBigInt64( position, littleEndian );
  }
  else
  {
    letvalue = 0n;
    let isNegative = ( view.getUint8( position + ( littleEndian ? 7 : 0 ) ) & 0x80 ) > 0;
    let carrying = true;
    for ( let i = 0; i < 8; i++ )
    {
      letbyte = view.getUint8( position + ( littleEndian ? i : 7 - i ) );
      if ( isNegative )
      {
        if ( carrying )
        {
          if ( byte != 0x00 )
          {
            byte = (~(byte - 1))&0xFF;
            carrying = false;
          }
        }
        else
        {
          byte = (~byte)&0xFF;
        }
      }
      value += BigInt(byte) * 256n**BigInt(i);
    }
    if ( isNegative )
    {
      value = -value;
    }
    returnvalue;
  }
}

Tests:

functiongetBigInt64( view, position, littleEndian = false)
{
  if ( "getBigInt64"inDataView.prototype )
  {
    return view.getBigInt64( position, littleEndian );
  }
  else
  {
    let value = 0n;
    let isNegative = ( view.getUint8( position + ( littleEndian ? 7 : 0 ) ) & 0x80 ) > 0;
    let carrying = true;
    for ( let i = 0; i < 8; i++ )
    {
      let byte = view.getUint8( position + ( littleEndian ? i : 7 - i ) );
      if ( isNegative )
      {
        if ( carrying )
        {
          if ( byte != 0x00 )
          {
            byte = (~(byte - 1))&0xFF;
            carrying = false;
          }
        }
        else
        {
          byte = (~byte)&0xFF;
        }
      }
      value += BigInt(byte) * 256n**BigInt(i);
    }
    if ( isNegative )
    {
      value = -value;
    }
    return value;
  }
}

// [byteArray, littleEndian, expectedValue]const testValues = [
  // big-endian
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0xff]),  false, 255n], 
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0xff, 0xff]),  false, 65535n],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0xff, 0xff, 0xff, 0xff]),  false, 4294967295n],
  [newUint8Array([0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00]),  false, 4294967296n],
  [newUint8Array([0x00, 0x1f, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff]),  false, 9007199254740991n],
  [newUint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, 9007199254740992n],
  [newUint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x01]),  false, 9007199254740993n],
  [newUint8Array([0x7F, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  false, (2n**63n)-1n],
  [newUint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  false, -1n],
  [newUint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0x00]),  false, -256n],
  [newUint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFE, 0xFF]),  false, -257n],
  [newUint8Array([0x80, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, -(2n**63n)],

  // little-endian
  [newUint8Array([0xff, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 255n], 
  [newUint8Array([0xff, 0xff, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  true, 65535n],
  [newUint8Array([0xff, 0xff, 0xff, 0xff,  0x00, 0x00, 0x00, 0x00]),  true, 4294967295n],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x01, 0x00, 0x00, 0x00]),  true, 4294967296n],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x01, 0x00, 0x00]),  true, 1099511627776n],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x01, 0x00]),  true, 281474976710656n],
  [newUint8Array([0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0x1f, 0x00]),  true, 9007199254740991n],
  [newUint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0x7F]),  true, (2n**63n)-1n],
  [newUint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  true, -1n],
  [newUint8Array([0x00, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  true, -256n],
  [newUint8Array([0xFF, 0xFE, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  true, -257n],
  [newUint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x80]),  true, -(2n**63n)]
];

testValues.forEach(
  function( [bytes, littleEndian, expectedValue] ) {
    const val = getBigInt64( newDataView(bytes.buffer), 0, littleEndian );
    console.log(
      val === expectedValue
                ? 'pass'
                : `FAIL. expected ${expectedValue}, received ${val}` );
  }
);

Post a Comment for "How To Read 64-bit Integer From An Arraybuffer / Dataview In Javascript"