Как читать 64-битное целое число из ArrayBuffer / DataView в JavaScript - PullRequest
0 голосов
/ 01 ноября 2018

С учетом 64-битного (8-байтового) байта с прямым порядком байтов ArrayBuffer байтов, как мы можем прочитать 64-битные целочисленные значения в JavaScript?

Я экспериментировал и придумал это, но есть ли более элегантное решение, учитывая, что 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 голосов
/ 25 марта 2019

Некоторые браузеры начинают поддерживать экспериментальный 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}` );
  }
);
0 голосов
/ 01 ноября 2018

Основываясь на исходном эксперименте и предложении / исправлении 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...