我需要在 JavaScript 中获取大整数的字节。
我尝试了几个大整数库,但实际提供此功能的库不起作用。
我不太确定如何自己实现这个,给定一个包含大量数字的字符串,这通常是库允许访问的。
是否有一个库可以运行并允许执行此操作? 或者它实际上并不难,我只是错过了一些东西?
我在谷歌上搜索 JavaScript 中这个问题的快速而优雅的解决方案,但我唯一找到的是基于中间十六进制字符串的转换方法。不幸的是,这肯定是次优的,并且该代码也不适合我。因此,我实现了自己的代码,并想将其发布作为我自己问题的答案,但找到了这个。
首先,我会回答相反的问题,因为它更能说明问题。
对于我们来说,字节数组是什么?这是 256 基数字系统中的数字,我们希望将其转换为更方便我们使用的 10 基(十进制)系统。
例如,我们采用一个字节数组
[AA][BB][CC][DD](1 个字节为 8 位或 2 个十六进制数字)。
根据我们从哪一侧开始(参见https://en.wikipedia.org/wiki/Endianness),我们可以将其读作:
我们在这里使用little-endian。所以,我们的数组 [AA][BB][CC][DD] 编码的数字是:
AA + BB*256 + CC*256^2 + DD*256^3
= 170 + 187*256 + 204*65536 + 221*16777216
= 170 + 47872 + 13369344 + 3707764736
= 3721182122
要将数字写入字节数组,我们必须执行相反的操作,即使用十进制数字来查找 256 进制数字系统中的所有数字。我们取相同的号码:3721182122
要找到它的最低有效字节(https://en.wikipedia.org/wiki/Bit_numbering#Least_significant_byte),我们只需将其除以 256。余数代表更高的数字。因此,我们再次将余数除以 256,依此类推,直到得到 0 余数:
3721182122 = 14535867*256 + 170
14535867 = 56780*256 + 187
56780 = 221*256 + 204
221 = 0*256 + 221
因此,结果为十进制的 [170][187][204][221],十六进制的 [AA][BB][CC][DD]。
现在,这是使用
NodeJS
库在 big-integer
中编码的算法。
const BigInteger = require('big-integer');
const zero = BigInteger(0);
const one = BigInteger(1);
const n256 = BigInteger(256);
function fromLittleEndian(bytes) {
let result = zero;
let base = one;
bytes.forEach(function (byte) {
result = result.add(base.multiply(BigInteger(byte)));
base = base.multiply(n256);
});
return result;
}
function fromBigEndian(bytes) {
return fromLittleEndian(bytes.reverse());
}
function toLittleEndian(bigNumber) {
let result = new Uint8Array(32);
let i = 0;
while (bigNumber.greater(zero)) {
result[i] = bigNumber.mod(n256);
bigNumber = bigNumber.divide(n256);
i += 1;
}
return result;
}
function toBigEndian(bytes) {
return toLittleEndian(bytes).reverse();
}
console.log('Reading BigInteger from an array of bytes');
let bigInt = fromLittleEndian(new Uint8Array([170, 187, 204, 221]));
console.log(bigInt.toString());
console.log('Writing BigInteger to an array of bytes');
let bytes = toLittleEndian(bigInt);
console.log(bytes);
我已经为这种方法编写了小基准。欢迎大家修改为自己的转换方法并与我的进行比较。
let yourBigInt = 11111n;
let arrayLen = 1500;
let array = new Uint8Array(arrayLen);
while (yourBigInt > 0n) {
array[--arrayLen] = Number(yourBigInt & 255n);
yourBigInt >>= 8n;
}
您还可以从 Uint8Array 中提取 BigInt:
array.reduce((prev, curr) => BigInt(prev) * 256n + BigInt(curr));
我有一个可与浏览器支持的 BigInt 配合使用的版本:
const big0 = BigInt(0)
const big1 = BigInt(1)
const big8 = BigInt(8)
bigToUint8Array(big: bigint) {
if (big < big0) {
const bits: bigint = (BigInt(big.toString(2).length) / big8 + big1) * big8
const prefix1: bigint = big1 << bits
big += prefix1
}
let hex = big.toString(16)
if (hex.length % 2) {
hex = '0' + hex
}
const len = hex.length / 2
const u8 = new Uint8Array(len)
var i = 0
var j = 0
while (i < len) {
u8[i] = parseInt(hex.slice(j, j + 2), 16)
i += 1
j += 2
}
return u8
}
我有一个 BigDecimal 实现,可以将字节作为任意精度大十进制发送和接收:https://jackieli.dev/posts/bigint-to-uint8array/