简体   繁体   English

在 Javascript 中将 Uint8Array 转换为 BigInt

[英]Converting Uint8Array to BigInt in Javascript

I have found 3 methods to convert Uint8Array to BigInt and all of them give different results for some reason.我找到了 3 种将 Uint8Array 转换为 BigInt 的方法,并且由于某种原因,它们都给出了不同的结果。 Could you please tell me which one is correct and which one should I use?你能告诉我哪一个是正确的,我应该使用哪一个?

  1. Using bigint-conversion library.使用bigint 转换库。 We can use bigintConversion.bufToBigint() function to get a BigInt.我们可以使用bigintConversion.bufToBigint() function 来获取 BigInt。 The implementation is as follows:实现如下:
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. Using DataView:使用数据视图:
let view = new DataView(arr.buffer, 0);
let result = view.getBigUint64(0, true);
  1. Using a FOR loop:使用 FOR 循环:
let result = BigInt(0);
for (let i = arr.length - 1; i >= 0; i++) {
  result = result * BigInt(256) + BigInt(arr[i]);
}

I'm honestly confused which one is right since all of them give different results but do give results.老实说,我很困惑哪一个是正确的,因为它们都给出了不同的结果,但确实给出了结果。

I'm fine with either BE or LE but I'd just like to know why these 3 methods give a different result.我对 BE 或 LE 都很好,但我只想知道为什么这 3 种方法会给出不同的结果。

One reason for the different results is that they use different endianness.结果不同的一个原因是它们使用了不同的字节序。

Let's turn your snippets into a form where we can execute and compare them:让我们将您的代码片段转换为我们可以执行和比较它们的形式:

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

Note that this includes a bug fix for method3: where you wrote for (let i = arr.length - 1; i >= 0; i++) , you clearly meant i-- at the end.请注意,这包括对方法 3 的错误修复:在您for (let i = arr.length - 1; i >= 0; i++)编写的地方,您显然是指最后的i--

For "method1" this prints: ffeeddccbbaa998877665544332211对于“method1”,打印: ffeeddccbbaa998877665544332211
Because method1 is a big-endian conversion (first byte of the array is most-significant part of the result) without size limit.因为 method1 是一个大端转换(数组的第一个字节是结果的最重要部分),没有大小限制。

For "method2" this prints: 8899aabbccddeeff对于“method2”,打印: 8899aabbccddeeff
Because method2 is a little-endian conversion (first byte of the array is least significant part of the result) limited to 64 bits.因为method2是一个little-endian转换(数组的第一个字节是结果的最低有效部分)限制为64位。
If you switch the second getBigUint64 argument from true to false , you get big-endian behavior: ffeeddccbbaa9988 .如果将第二个getBigUint64参数从true切换为false ,则会得到大端行为: ffeeddccbbaa9988
To eliminate the size limitation, you'd have to add a loop: using getBigUint64 you can get 64-bit chunks, which you can assemble using shifts similar to method1 and method3.为了消除大小限制,您必须添加一个循环:使用getBigUint64您可以获得 64 位块,您可以使用类似于方法 1 和方法 3 的移位来组装它们。

For "method3" this prints: 112233445566778899aabbccddeeff对于“method3”,打印: 112233445566778899aabbccddeeff
Because method3 is a little-endian conversion without size limit.因为method3是小端转换,没有大小限制。 If you reverse the for -loop's direction, you'll get the same big-endian behavior as method1: result * 256n gives the same value as result << 8n ;如果您反转for循环的方向,您将获得与 method1 相同的大端行为: result * 256n给出与result << 8n相同的值; the latter is a bit faster.后者要快一些。
(Side note: BigInt(0) and BigInt(256) are needlessly verbose, just write 0n and 256n instead. Additional benefit: 123456789123456789n does what you'd expect, BigInt(123456789123456789) does not.) (旁注: BigInt(0)BigInt(256)是不必要的冗长,只需写0n256n 。额外的好处: 123456789123456789n符合您的预期, BigInt(123456789123456789)没有。)

So which method should you use?那么你应该使用哪种方法呢? That depends on:这取决于:
(1) Do your incoming arrays assume BE or LE encoding? (1) 您传入的 arrays 是否采用 BE 或 LE 编码?
(2) Are your BigInts limited to 64 bits or arbitrarily large? (2) 您的 BigInts 是否限制为 64 位或任意大?
(3) Is this performance-critical code, or are all approaches "fast enough"? (3) 这是性能关键代码,还是所有方法都“足够快”?

Taking a step back: if you control both parts of the overall process (converting BigInts to Uint8Array, then transmitting/storing them, then converting back to BigInt), consider simply using hexadecimal strings instead: that'll be easier to code, easier to debug, and significantly faster.退后一步:如果您控制整个过程的两个部分(将 BigInts 转换为 Uint8Array,然后传输/存储它们,然后转换回 BigInt),请考虑简单地使用十六进制字符串:这将更容易编码,更容易调试,并且明显更快。 Something like:就像是:

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