咳咳,大家好!今天咱们来聊聊 JavaScript 里 BigInt
的两个小能手:asUintN
和 asIntN
。别看名字有点绕,其实它们是用来帮助我们处理特定位宽的整数的,就像给数字穿上合身的衣服一样。
BigInt 是个啥?
在深入 asUintN
和 asIntN
之前,咱们先快速回顾一下 BigInt
。JavaScript 原生的 Number
类型有一定的精度限制,超过这个范围的整数计算可能会出现精度丢失。BigInt
的出现就是为了解决这个问题,它可以表示任意精度的整数。
const maxSafeInteger = Number.MAX_SAFE_INTEGER; // 9007199254740991
const beyondMax = maxSafeInteger + 1; // 9007199254740992 (仍然正确)
const wayBeyondMax = maxSafeInteger + 2; // 9007199254740992 (精度丢失!)
const bigIntMax = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
const bigIntBeyondMax = bigIntMax + 1n; // 9007199254740992n
const bigIntWayBeyondMax = bigIntMax + 2n; // 9007199254740993n
看到没?BigInt
后面要加个 n
,表示这是一个 BigInt 类型的值。
为什么要限制位宽?
现在问题来了,既然 BigInt
可以表示任意大小的整数,那为什么我们还需要限制它的位宽呢?想象一下,你用 BigInt
来表示一个 32 位的无符号整数,但你不小心给它赋了一个很大的值,比如 2 的 64 次方。虽然 BigInt
可以存储这个值,但如果你希望它表现得像一个 32 位的无符号整数,就需要一种方法来将它限制在 32 位的范围内。
这种情况在处理底层数据、网络协议、加密算法等场景中非常常见。例如,IP 地址是 32 位的无符号整数,颜色值通常用 8 位或 16 位的整数表示,等等。
BigInt.asUintN(bits, bigint)
:无符号整数的裁剪师
BigInt.asUintN(bits, bigint)
方法的作用就是将 bigint
转换为一个 bits
位的无符号整数。简单来说,它会截断超出指定位数的位,并返回一个在 0 到 2bits – 1 范围内的 BigInt
值。
它的工作原理是:
- 取模运算:
bigint % (2n ** BigInt(bits))
计算bigint
除以 2 的bits
次方的余数。这实际上就是将bigint
限制在 0 到 2bits – 1 的范围内。
举个例子:
const num = 42n;
const unsigned8Bit = BigInt.asUintN(8, num); // 42n (在 0-255 范围内)
const unsigned4Bit = BigInt.asUintN(4, num); // 10n (42 % 16 = 10)
console.log(unsigned8Bit); // 42n
console.log(unsigned4Bit); // 10n
如果 bigint
是负数,asUintN
会先将其转换为正数,然后再进行截断。
const negativeNum = -42n;
const unsigned8BitNegative = BigInt.asUintN(8, negativeNum); // 214n
// (-42 % 256 + 256) % 256 = 214
console.log(unsigned8BitNegative); // 214n
再来几个例子,更深入地理解它的工作方式:
bits | bigint | BigInt.asUintN(bits, bigint) | 解释 |
---|---|---|---|
8 | 255n | 255n | 255 在 0-255 范围内 |
8 | 256n | 0n | 256 % 256 = 0 |
8 | 257n | 1n | 257 % 256 = 1 |
8 | -1n | 255n | (-1 % 256 + 256) % 256 = 255 |
8 | -256n | 0n | (-256 % 256 + 256) % 256 = 0 |
4 | 15n | 15n | 15 在 0-15 范围内 |
4 | 16n | 0n | 16 % 16 = 0 |
4 | -1n | 15n | (-1 % 16 + 16) % 16 = 15 |
4 | 255n | 15n | 255 % 16 = 15 |
64 | 2n**64n-1n | 18446744073709551615n | 264 – 1 在 0 到 264 – 1 范围内 |
64 | 2n**64n | 0n | 264 % 264 = 0 |
BigInt.asIntN(bits, bigint)
:有符号整数的整形师
BigInt.asIntN(bits, bigint)
方法则将 bigint
转换为一个 bits
位的有符号整数。它会截断超出指定位数的位,并返回一个在 -2bits-1 到 2bits-1 – 1 范围内的 BigInt
值。
它的工作原理稍微复杂一些:
- 取模运算: 和
asUintN
一样,先计算bigint % (2n ** BigInt(bits))
。 - 判断是否超出范围: 如果余数大于或等于 2bits-1,则从余数中减去 2bits,将其转换为负数。
举个例子:
const num = 42n;
const signed8Bit = BigInt.asIntN(8, num); // 42n (在 -128 到 127 范围内)
const signed4Bit = BigInt.asIntN(4, num); // -6n (42 % 16 = 10, 10 - 16 = -6)
console.log(signed8Bit); // 42n
console.log(signed4Bit); // -6n
如果 bigint
本身就是负数,asIntN
也会正确地将其转换为指定范围内的有符号整数。
const negativeNum = -42n;
const signed8BitNegative = BigInt.asIntN(8, negativeNum); // -42n
const signed4BitNegative = BigInt.asIntN(4, negativeNum); // 10n
// -42 % 16 = -10, -10 + 16 = 6, 6 >= 8 (2**(4-1)) ? 6 -16 = -10 这里算错了,是 10
console.log(signed8BitNegative); // -42n
console.log(signed4BitNegative); // 10n 这里也错了
修正一下上面的例子和解释:
const negativeNum = -42n;
const signed8BitNegative = BigInt.asIntN(8, negativeNum); // -42n
const signed4BitNegative = BigInt.asIntN(4, negativeNum); // -10n
// -42 % (2n**4n) = -10n
// 因为 -10n < 2n**(4n-1n)也就是8n, 所以结果是 -10n
console.log(signed8BitNegative); // -42n
console.log(signed4BitNegative); // -10n
再来一些更全面的例子:
bits | bigint | BigInt.asIntN(bits, bigint) | 解释 |
---|---|---|---|
8 | 127n | 127n | 127 在 -128 到 127 范围内 |
8 | 128n | -128n | 128 % 256 = 128, 128 >= 128 (27) ? 128 – 256 = -128 |
8 | -128n | -128n | -128 在 -128 到 127 范围内 |
8 | -129n | 127n | -129 % 256 = -129 + 256 = 127 |
4 | 7n | 7n | 7 在 -8 到 7 范围内 |
4 | 8n | -8n | 8 % 16 = 8, 8 >= 8 (23) ? 8 – 16 = -8 |
4 | -8n | -8n | -8 在 -8 到 7 范围内 |
4 | -9n | 7n | -9 % 16 = -9 + 16 = 7 |
16 | 2n**15n-1n | 32767n | 215 – 1 在 -215 到 215 – 1 范围内 |
16 | 2n**15n | -32768n | 215 % 216 = 215, 215 >= 215 ? 215 – 216 = -215 |
应用场景
这两个方法在以下场景中非常有用:
- 处理底层数据: 例如,从二进制文件中读取数据,需要将读取到的
BigInt
值转换为特定位宽的整数。 - 网络协议: 很多网络协议都使用固定位宽的整数来表示数据,例如 IP 地址、端口号等。
- 加密算法: 一些加密算法也需要处理特定位宽的整数。
- 模拟硬件: 在模拟硬件行为时,需要模拟硬件的位宽限制。
代码示例:模拟 8 位加法器
让我们来模拟一个简单的 8 位加法器:
function add8Bit(a, b) {
const sum = a + b;
return BigInt.asUintN(8, sum);
}
console.log(add8Bit(100n, 50n)); // 150n
console.log(add8Bit(200n, 100n)); // 44n (因为 300 % 256 = 44)
console.log(add8Bit(-10n, 20n)); // 10n
console.log(add8Bit(255n, 1n)); // 0n (溢出)
代码示例:处理 IPv4 地址
IPv4 地址是 32 位的无符号整数,通常以点分十进制表示。我们可以使用 asUintN
将点分十进制表示的 IPv4 地址转换为 BigInt
值:
function ipv4ToBigInt(ipAddress) {
const parts = ipAddress.split('.').map(Number);
if (parts.length !== 4) {
throw new Error('Invalid IPv4 address');
}
let bigIntValue = 0n;
for (let i = 0; i < 4; i++) {
bigIntValue = bigIntValue * 256n + BigInt(parts[i]);
}
return BigInt.asUintN(32, bigIntValue);
}
const ipAddress = '192.168.1.1';
const bigIntIp = ipv4ToBigInt(ipAddress);
console.log(bigIntIp); // 3232235777n
反过来,我们也可以将 BigInt
转换为点分十进制表示的 IPv4 地址:
function bigIntToIpv4(bigIntValue) {
bigIntValue = BigInt.asUintN(32,bigIntValue);
const part1 = Number(bigIntValue >> 24n & 255n); // 相当于除以 256 的三次方
const part2 = Number(bigIntValue >> 16n & 255n); // 相当于除以 256 的二次方
const part3 = Number(bigIntValue >> 8n & 255n); // 相当于除以 256 的一次方
const part4 = Number(bigIntValue & 255n); // 相当于除以 256 的零次方
return `${part1}.${part2}.${part3}.${part4}`;
}
console.log(bigIntToIpv4(3232235777n)); // "192.168.1.1"
总结
BigInt.asUintN
和 BigInt.asIntN
是 BigInt
类型中非常有用的两个方法,它们可以帮助我们将 BigInt
值限制在特定的位宽范围内,这在处理底层数据、网络协议、加密算法等场景中非常重要。掌握了这两个方法,你就可以更加灵活地使用 BigInt
,编写出更加健壮和高效的代码。
记住,asUintN
负责无符号整数的裁剪,而 asIntN
负责有符号整数的整形。 就像裁缝师傅一样,它们能让你的 BigInt
数字穿上最合身的 "位宽" 衣裳!
今天的讲座就到这里,希望大家有所收获!下次再见!