繁体   English   中英

在 Javascript 中将 Uint8Array 转换为 BigInt

[英]Converting Uint8Array to BigInt in Javascript

我找到了 3 种将 Uint8Array 转换为 BigInt 的方法,并且由于某种原因,它们都给出了不同的结果。 你能告诉我哪一个是正确的,我应该使用哪一个?

  1. 使用bigint 转换库。 我们可以使用bigintConversion.bufToBigint() function 来获取 BigInt。 实现如下:
export function bufToBigint (buf: ArrayBuffer|TypedArray|Buffer): bigint {
  let bits = 8n
  if (ArrayBuffer.isView(buf)) bits = BigInt(buf.BYTES_PER_ELEMENT * 8)
  else buf = new Uint8Array(buf)

  let ret = 0n
  for (const i of (buf as TypedArray|Buffer).values()) {
    const bi = BigInt(i)
    ret = (ret << bits) + bi
  }
  return ret
}
  1. 使用数据视图:
let view = new DataView(arr.buffer, 0);
let result = view.getBigUint64(0, true);
  1. 使用 FOR 循环:
let result = BigInt(0);
for (let i = arr.length - 1; i >= 0; i++) {
  result = result * BigInt(256) + BigInt(arr[i]);
}

老实说,我很困惑哪一个是正确的,因为它们都给出了不同的结果,但确实给出了结果。

我对 BE 或 LE 都很好,但我只想知道为什么这 3 种方法会给出不同的结果。

结果不同的一个原因是它们使用了不同的字节序。

让我们将您的代码片段转换为我们可以执行和比较它们的形式:

let source_array = new Uint8Array([
    0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 
    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]);
let buffer = source_array.buffer;

function method1(buf) {
  let bits = 8n
  if (ArrayBuffer.isView(buf)) {
    bits = BigInt(buf.BYTES_PER_ELEMENT * 8)
  } else {
    buf = new Uint8Array(buf)
  }

  let ret = 0n
  for (const i of buf.values()) {
    const bi = BigInt(i)
    ret = (ret << bits) + bi
  }
  return ret
}

function method2(buf) {
  let view = new DataView(buf, 0);
  return view.getBigUint64(0, true);
}

function method3(buf) {
  let arr = new Uint8Array(buf);
  let result = BigInt(0);
  for (let i = arr.length - 1; i >= 0; i--) {
    result = result * BigInt(256) + BigInt(arr[i]);
  }
  return result;
}

console.log(method1(buffer).toString(16));
console.log(method2(buffer).toString(16));
console.log(method3(buffer).toString(16));

请注意,这包括对方法 3 的错误修复:在您for (let i = arr.length - 1; i >= 0; i++)编写的地方,您显然是指最后的i--

对于“method1”,打印: ffeeddccbbaa998877665544332211
因为 method1 是一个大端转换(数组的第一个字节是结果的最重要部分),没有大小限制。

对于“method2”,打印: 8899aabbccddeeff
因为method2是一个little-endian转换(数组的第一个字节是结果的最低有效部分)限制为64位。
如果将第二个getBigUint64参数从true切换为false ,则会得到大端行为: ffeeddccbbaa9988
为了消除大小限制,您必须添加一个循环:使用getBigUint64您可以获得 64 位块,您可以使用类似于方法 1 和方法 3 的移位来组装它们。

对于“method3”,打印: 112233445566778899aabbccddeeff
因为method3是小端转换,没有大小限制。 如果您反转for循环的方向,您将获得与 method1 相同的大端行为: result * 256n给出与result << 8n相同的值; 后者要快一些。
(旁注: BigInt(0)BigInt(256)是不必要的冗长,只需写0n256n 。额外的好处: 123456789123456789n符合您的预期, BigInt(123456789123456789)没有。)

那么你应该使用哪种方法呢? 这取决于:
(1) 您传入的 arrays 是否采用 BE 或 LE 编码?
(2) 您的 BigInts 是否限制为 64 位或任意大?
(3) 这是性能关键代码,还是所有方法都“足够快”?

退后一步:如果您控制整个过程的两个部分(将 BigInts 转换为 Uint8Array,然后传输/存储它们,然后转换回 BigInt),请考虑简单地使用十六进制字符串:这将更容易编码,更容易调试,并且明显更快。 就像是:

function serialize(bigint) {
  return "0x" + bigint.toString(16);
}
function deserialize(serialized_bigint) {
  return BigInt(serialized_bigint);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM