如何从JavaScript中的ArrayBuffer / DataView读取64位整数

时间:2018-11-01 14:49:47

标签: javascript arraybuffer

给定一个64位(8字节)的低位ArrayBuffer字节,我们如何在JavaScript中读取64位整数值?

我进行了实验并提出了解决方案,但是鉴于DataView尚未提供getUint64(),还有更好的解决方案吗?

const bytes = new Uint8Array([ 0xff,0xff,0xff,0xff,   0xff,0xff,0xff,0xff ]);
//                             [-----  left  -----]   [----  right  ----] 

const view = new DataView(bytes.buffer);

// split 64-bit number into two 32-bit numbers
const left = view.getUint32(0, true);  // 4294967295
const right = view.getUint32(4, true); // 4294967295

// combine the 2 32-bit numbers using max 32-bit val 0xffffffff
const combined = left + 2**32*right;

console.log('combined', combined);
// 18,446,744,073,709,552,000  is returned Javascript for "combined"
// 18,446,744,073,709,551,615  is max uint64 value
// some precision is lost since JS doesn't support 64-bit ints, but it's close enough

2 个答案:

答案 0 :(得分:2)

根据原始实验和Sebastian Speitel的建议/修正,此函数返回一个64位值,直到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;
}


经过测试:

// [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);
}

答案 1 :(得分:0)

某些浏览器开始支持实验性BigInt全局对象:

  

BigInt是一个内置对象,它提供了一种表示大于2 53 的整数的方法,这是JavaScript可以使用Number原语可靠地表示的最大数字。

如果仅定位这些浏览器,则可以使用它来获得比Number支持的值更大的值。此外,Chrome当前支持DataView.getBigInt64( position, littleEndian )DataView.getBigUint64( position, littleEndian )函数,它们返回BigInt值。

读取64位无符号值

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;
  }
}

读取64位带符号的值

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;
  }
}

测试

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}` );
  }
);