How to Read 64-bit Integer from an ArrayBuffer / DataView in JavaScript

∥☆過路亽.° 提交于 2019-12-01 06:44:59

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 parts
  const left =  this.getUint32(byteOffset, littleEndian);
  const right = this.getUint32(byteOffset+4, littleEndian);

  // combine the two 32-bit values
  const 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
  [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], // precision lost
  [new Uint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x01]),  false, 9007199254740992], // 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]) {
  const val = new DataView(bytes.buffer).getUint64(0, littleEndian);
  console.log(val === expectedValue? 'pass' : 'FAIL. expected '+expectedValue+', received '+val);
}

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 253, 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

function getBigUint64( view, position, littleEndian = false )
{
  if ( "getBigUint64" in DataView.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
  {
    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;
  }
}

Tests:

function getBigInt64( view, position, littleEndian = false )
{
  if ( "getBigInt64" in DataView.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
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0xff]),  false, 255n], 
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0xff, 0xff]),  false, 65535n],
  [new Uint8Array([0x00, 0x00, 0x00, 0x00,  0xff, 0xff, 0xff, 0xff]),  false, 4294967295n],
  [new Uint8Array([0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00]),  false, 4294967296n],
  [new Uint8Array([0x00, 0x1f, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff]),  false, 9007199254740991n],
  [new Uint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, 9007199254740992n],
  [new Uint8Array([0x00, 0x20, 0x00, 0x00,  0x00, 0x00, 0x00, 0x01]),  false, 9007199254740993n],
  [new Uint8Array([0x7F, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  false, (2n**63n)-1n],
  [new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0xFF]),  false, -1n],
  [new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFF, 0x00]),  false, -256n],
  [new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF,  0xFF, 0xFF, 0xFE, 0xFF]),  false, -257n],
  [new Uint8Array([0x80, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00]),  false, -(2n**63n)],

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

testValues.forEach(
  function( [bytes, littleEndian, expectedValue] ) {
    const val = getBigInt64( new DataView(bytes.buffer), 0, littleEndian );
    console.log(
      val === expectedValue
                ? 'pass'
                : `FAIL. expected ${expectedValue}, received ${val}` );
  }
);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!