字节数组到Uint64作为字符串

时间:2017-08-02 08:51:22

标签: javascript string ecmascript-6 int64 uint64

让我们考虑以下情况。

Go例程创建一个字节数组,其中包含8字节Big Endian 5577006791947779410 Uint64 编号[77, 101, 130, 33, 7, 252, 253, 82]

在JavaScript代码中,我将这些字节作为Uint8Array接收。我们知道JavaScript当前不支持 Uint64 作为安全数字类型,并且不能对大于32位的整数执行按位运算,因此buf[0] << 56之类的东西将无法工作。

那么将这些字节直接解码为数字字符串"5577006791947779410"的过程是什么?

PS 我知道有plenty of libraries用于处理JavaScript中的大整数,但通常它们很庞大并且提供了大量的数学运算,这里我不需要。我正在寻找一个简单的现代直接解决方案,只需解码BE-packed Uint64 Int64 字节到数字字符串。你有什么想法吗?

4 个答案:

答案 0 :(得分:8)

编辑:为了转换(U)int64,我现在肯定会推荐@ LS_DEV的解决方案。只有在拥有未知或更大的字节数时,我才会使用我的解决方案。

我从https://stackoverflow.com/a/21668344/3872370开始修改它:

&#13;
&#13;
function Int64ToString(bytes, isSigned) {
  const isNegative = isSigned && bytes.length > 0 && bytes[0] >= 0x80;
  const digits = [];
  bytes.forEach((byte, j) => {
    if(isNegative)
      byte = 0x100 - (j == bytes.length - 1 ? 0 : 1) - byte;
    for(let i = 0; byte > 0 || i < digits.length; i++) {
      byte += (digits[i] || 0) * 0x100;
      digits[i] = byte % 10;
      byte = (byte - digits[i]) / 10;
    }
  });
  return (isNegative ? '-' : '') + digits.reverse().join('');
}

const tests = [
  {
    inp: [77, 101, 130, 33, 7, 252, 253, 82],
    signed: false,
    expectation: '5577006791947779410'
  },
  {
    inp: [255, 255, 255, 255, 255, 255, 255, 255],
    signed: true,
    expectation: '-1'
  },
];

tests.forEach(test => {
  const result = Int64ToString(test.inp, test.signed);
  console.log(`${result} ${result !== test.expectation ? '!' : ''}=== ${test.expectation}`);
});
&#13;
&#13;
&#13;

首先通过检查最顶部位是否设置(bytes[0] > 128)来计算符号。对于负数,这些位必须被否定(255 - byte),并且必须将1添加到数字中(因此对于最后一个字节,256而不是255

forEach循环的基本思想是将每个字节分成十进制数字byte % 10并计算下一个数字的开销(byte - digits[i]) / 10Math.floor(byte / 10)。对于下一个字节,必须添加最后一个字节的移位结果。数字(byte += digits[i] * 256digits[i] << 8)。

该代码针对简洁性,简单性和灵活性进行了优化。如果您使用的是字符串而不是字节或数字,并且不想使用任何库,那么转换性能似乎并不重要。否则,该函数可以针对性能进行优化:可以同时处理多达四个字节,另外一个只需要替换0x1000x80(在一个情况下只剩下两个字节组) U)Int64)可以展开forEach循环。对小数位进行分组可能不会提高性能,因为生成的字符串必须用零填充,因此需要在最终结果中删除前导零。

答案 1 :(得分:3)

另一种方法:在两个uint32中划分问题以保持计算的可管理性。

考虑更低和更高的uint32(lh)。完整号码可以写为h*0x100000000+l。考虑到十进制,也可以考虑较低的9位数和更高的数字(ldhd):ld=(h*0x100000000+l)%1000000000hd=(h*0x100000000+l)/1000000000。通过一些算术和代数运算符属性,人们可以将这些操作分解为安全的一半&#34; 64位操作并在结束时撰写字符串。

&#13;
&#13;
function int64_to_str(a, signed) {
  const negative = signed && a[0] >= 128;
  const H = 0x100000000, D = 1000000000;
  let h = a[3] + a[2] * 0x100 + a[1] * 0x10000 + a[0]*0x1000000;
  let l = a[7] + a[6] * 0x100 + a[5] * 0x10000 + a[4]*0x1000000;
  if(negative) {
     h = H - 1 - h;
     l = H - l;
  }
  const hd = Math.floor(h * H / D + l / D);
  const ld = (((h % D) * (H % D)) % D + l) % D;
  const ldStr = ld + '';
  return (negative ? '-' : '') +
         (hd != 0 ? hd + '0'.repeat(9 - ldStr.length) : '') + ldStr;
}

let result = int64_to_str([77, 101, 130, 33, 7, 252, 253, 82], false);
let expectation = '5577006791947779410';
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation);

result = int64_to_str([255, 255, 255, 255, 255, 255, 255, 255], true);
expectation = '-1';
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation);
&#13;
&#13;
&#13;

尽管(h % D) * (H % D)可能会大于Number.MAX_SAFE_INTEGER,但在评论中详细说明算法仍然有效,因为丢失的位数仍为零。

答案 2 :(得分:1)

这是我的解决方案。总体战略是:

  • 如果数字为负数,则使用2的补码将其否定,并在最后添加负号
  • 将任意大小的数字表示为从0到9
  • 的LE数组
  • 对于Uint8Array中的每个字节(从最高到最低),将运行总数乘以256并将新字节的值加上
  • 要将数字乘以256,请将其加倍8倍(自2 ** 8 == 256起)
  • 要添加两个数字,请使用小学算法:
    • 以最低有效数字开头
    • 添加两个数字的相应数字
    • 得到的数字是总和模10;如果总和为10或更多,则进位为1,否则为0
    • 继续在进位中添加相应的数字,直到我们添加最高位数并且进位为0

关于速记的一些注释:

  • n1[i] || 0获取i的{​​{1}}位数。如果这已超过n1的结尾,我们将其视为0(想象在它们前面以无限0表示的数字)。与i相同。
  • n2生成一个布尔值,它会自动转换为数字(如果added > 9则为1,否则为0)
  • added >= 10检查其中任何一个加数中是否有更多数字,或者进位是否仍为非零
  • i < n1.length || i < n2.length || carry转换,例如String(b).split('').map(Number).reverse()100,然后'100',然后['1', '0', '0'],然后[1, 0, 0],以便在LE base-10中表示
  • [0, 0, 1]转换,例如result.reverse().join('')[0, 0, 1],然后[1, 0, 0]

代码:

'100'

答案 3 :(得分:1)

这是binding.recyclerView.setHasFixedSize(true) binding.recyclerView.layoutManager = LinearLayoutManager(this ,LinearLayoutManager.VERTICAL ,false) binding.recyclerView.adapter = customAdapter(this ,getList()) 版本 - 我无法想象交换 很难:

UInt64

<!DOCTYPE html> <html> <body> <span id='out1'></span> <br> <span id='out2'></span> <br> <span id='out3'></span> </body> <script> fnl=''; be=[77, 101, 130, 33, 7, 252, 253, 82]; function paddedBinary(n) { pad=''; sv=128; while (sv>n) {pad+='0';sv/=2;} return pad+n.toString(2); } for (let i=0;i<8;i++) fnl+=paddedBinary(be[i]); out1.textContent=fnl; dec=new Array(64); for (let i=0;i<64;i++) dec[i]=new Array(21).fill(0); function make2s() { dec[0][0]=1; for (let i=1;i<64;i++) { for (let j=0;j<21;j++) dec[i][j]=2*dec[i-1][j]; for (let j=0;j<21;j++) if (dec[i][j]>9) { dec[i][j]-=10; dec[i][j+1]++; } } } function int64add(v1,v2) { var res=new Array(21).fill(0); for (let i=0;i<21;i++) res[i]=v1[i]+v2[i]; for (let i=0;i<21;i++) if (res[i]>9) { res[i]-=10; res[i+1]++; } return res; } make2s(); for (let i=0;i<64;i++) out2.textContent+=dec[i]+' :: '; cv=new Array(21).fill(0); for (let i=0;i<fnl.length;i++) if (fnl[i]=='1') cv=int64add(cv,dec[63-i]); out3.textContent=cv; </script> </html>函数返回一个'完整的'8位二进制数,因此我们可以将'fnl'创建为BigEndian的64位字符串。

由于JavaScript没有完整的64位算术,我创建了paddedBinary()数组,将每个2的幂作为单独的数字存储,通过将每个前一个数字加倍并平滑数字。

然后剩下的就是添加我们想要的位,它使用类似的方法来平滑数十位。

(答案是反过来的!)