阐述 JavaScript 中的 BigInt.asUintN 和 BigInt.asIntN 方法在处理特定位宽整数时的作用。

咳咳,大家好!今天咱们来聊聊 JavaScript 里 BigInt 的两个小能手:asUintNasIntN。别看名字有点绕,其实它们是用来帮助我们处理特定位宽的整数的,就像给数字穿上合身的衣服一样。

BigInt 是个啥?

在深入 asUintNasIntN 之前,咱们先快速回顾一下 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 值。

它的工作原理是:

  1. 取模运算: 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 值。

它的工作原理稍微复杂一些:

  1. 取模运算:asUintN 一样,先计算 bigint % (2n ** BigInt(bits))
  2. 判断是否超出范围: 如果余数大于或等于 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.asUintNBigInt.asIntNBigInt 类型中非常有用的两个方法,它们可以帮助我们将 BigInt 值限制在特定的位宽范围内,这在处理底层数据、网络协议、加密算法等场景中非常重要。掌握了这两个方法,你就可以更加灵活地使用 BigInt,编写出更加健壮和高效的代码。

记住,asUintN 负责无符号整数的裁剪,而 asIntN 负责有符号整数的整形。 就像裁缝师傅一样,它们能让你的 BigInt 数字穿上最合身的 "位宽" 衣裳!

今天的讲座就到这里,希望大家有所收获!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注