中文

一份关于 JavaScript BigInt 类型的综合指南,涵盖其特性、用法以及在处理大整数运算中的应用。学习如何克服 JavaScript 的限制并精确执行复杂计算。

JavaScript BigInt:精通大整数运算

JavaScript 虽然是一种功能多样的语言,但在处理非常大的整数时存在限制。标准的 `Number` 类型只能精确表示到一定上限的整数,即 `Number.MAX_SAFE_INTEGER`。超出此限制,计算会变得不精确,导致意外结果。这时 BigInt 就派上用场了。BigInt 在 ECMAScript 2020 中引入,是一个内置对象,它提供了一种表示和操作任意大小整数的方法,超越了标准 `Number` 类型的限制。

理解 BigInt 的必要性

BigInt 出现之前,JavaScript 开发者必须依赖库或自定义实现来处理大整数计算。这些解决方案通常伴随着性能开销和复杂性的增加。BigInt 的引入提供了一种原生且高效处理大整数的方式,为各种领域的应用开辟了可能性,包括:

创建 BigInt 值

在 JavaScript 中创建 BigInt 值主要有两种方法:

  1. 使用 `BigInt()` 构造函数:此构造函数可以将数字、字符串或布尔值转换为 BigInt
  2. 使用 `n` 后缀:在整数字面量后附加 `n` 会创建一个 BigInt

示例:

使用 `BigInt()` 构造函数:


const bigIntFromNumber = BigInt(12345678901234567890);
const bigIntFromString = BigInt("98765432109876543210");
const bigIntFromBoolean = BigInt(true); // 结果为 1n
const bigIntFromFalseBoolean = BigInt(false); // 结果为 0n

console.log(bigIntFromNumber); // 输出: 12345678901234567890n
console.log(bigIntFromString); // 输出: 98765432109876543210n
console.log(bigIntFromBoolean); // 输出: 1n
console.log(bigIntFromFalseBoolean); // 输出: 0n

使用 `n` 后缀:


const bigIntLiteral = 12345678901234567890n;
console.log(bigIntLiteral); // 输出: 12345678901234567890n

重要提示:您不能在算术运算中直接混合使用 BigIntNumber 值。在执行计算之前,您需要将它们显式转换为相同类型。直接混合使用它们将导致 `TypeError`。

BigInt 算术运算

BigInt 支持大多数标准算术运算符,包括:

示例:


const a = 12345678901234567890n;
const b = 98765432109876543210n;

const sum = a + b;
const difference = a - b;
const product = a * b;
const quotient = a / 2n; // 注意:除法会向零取整
const remainder = a % 7n;
const power = a ** 3n; // 幂运算符合预期

console.log("和:", sum); // 输出: 和: 111111111011111111100n
console.log("差:", difference); // 输出: 差: -86419753208641975320n
console.log("积:", product); // 输出: 积: 1219326311370217957951669538098765432100n
console.log("商:", quotient); // 输出: 商: 6172839450617283945n
console.log("余数:", remainder); // 输出: 余数: 5n
console.log("幂:", power); // 输出: 幂: 187641281029182300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n

重要注意事项:

比较运算符

您可以使用标准比较运算符(`==`、`!=`、`<`、`>`、`<=`、`>=`)来比较 BigInt 值与其他 BigInt 值,甚至与 Number 值进行比较。但是,请注意类型强制转换的潜在问题。

示例:


const a = 10n;
const b = 20n;
const c = 10;

console.log(a == b);   // 输出: false
console.log(a != b);   // 输出: true
console.log(a < b);    // 输出: true
console.log(a > b);    // 输出: false
console.log(a <= b);   // 输出: true
console.log(a >= b);   // 输出: false

console.log(a == c);   // 输出: true (类型强制转换)
console.log(a === c);  // 输出: false (无类型强制转换)

最佳实践:在比较 BigIntNumber 值时,使用严格相等(`===`)和严格不相等(`!==`)以避免意外的类型强制转换。

在 BigInt 和 Number 之间转换

虽然不允许在 BigIntNumber 之间直接进行算术运算,但您可以在这两种类型之间进行转换。但是,请注意,当 BigInt 值超过 `Number.MAX_SAFE_INTEGER` 时,将其转换为 Number 可能会导致精度损失。

示例:


const bigIntValue = 9007199254740991n; // Number.MAX_SAFE_INTEGER
const numberValue = Number(bigIntValue); // 将 BigInt 转换为 Number
console.log(numberValue); // 输出: 9007199254740991

const largerBigIntValue = 9007199254740992n; // 超过 Number.MAX_SAFE_INTEGER
const largerNumberValue = Number(largerBigIntValue);
console.log(largerNumberValue); // 输出: 9007199254740992 (可能不精确)

const numberToBigInt = BigInt(12345); // 将 Number 转换为 BigInt
console.log(numberToBigInt); // 输出: 12345n

用例和示例

密码学

加密算法通常依赖于非常大的素数来确保安全性。BigInt 提供了一种高效表示和操作这些数字的方法。


// 示例:生成一个简单的(不安全的)密钥对
function generateKeyPair() {
  const p = 281n; // 一个素数
  const q = 283n; // 另一个素数
  const n = p * q; // 模数
  const totient = (p - 1n) * (q - 1n); // 欧拉函数

  // 选择一个 e (公钥指数) 满足 1 < e < totient 且 gcd(e, totient) = 1
  const e = 17n;

  // 计算 d (私钥指数) 满足 (d * e) % totient = 1
  let d = 0n;
  for (let i = 1n; i < totient; i++) {
    if ((i * e) % totient === 1n) {
      d = i;
      break;
    }
  }

  return {
    publicKey: { n, e },
    privateKey: { n, d },
  };
}

const keyPair = generateKeyPair();
console.log("公钥:", keyPair.publicKey);
console.log("私钥:", keyPair.privateKey);

注意:这只是一个用于演示目的的简化示例。现实世界中的密码学使用大得多的素数和更复杂的算法。

金融计算

在处理大额资金时,尤其是在国际交易中,精度至关重要。BigInt 可以防止舍入误差并确保计算准确。


// 示例:计算复利
function calculateCompoundInterest(principal, rate, time) {
  const principalBigInt = BigInt(principal * 100); // 转换为分
  const rateBigInt = BigInt(rate * 10000);       // 转换为万分之一
  const timeBigInt = BigInt(time);

  let amount = principalBigInt;
  for (let i = 0n; i < timeBigInt; i++) {
    amount = amount * (10000n + rateBigInt) / 10000n;
  }

  const amountInDollars = Number(amount) / 100;
  return amountInDollars;
}

const principal = 1000000; // $1,000,000
const rate = 0.05;    // 5% 利率
const time = 10;     // 10 年

const finalAmount = calculateCompoundInterest(principal, rate, time);
console.log("最终金额:", finalAmount); // 输出: 最终金额: 1628894.6267774413 (大约)

在这个例子中,我们将本金和利率转换为 BigInt 值以避免计算过程中的舍入误差。然后将结果转换回 Number 进行显示。

处理大 ID

在分布式系统中,跨多个服务器生成唯一 ID 可能具有挑战性。使用 BigInt 可以让您创建极大的、不太可能冲突的 ID。


// 示例:基于时间戳和服务器 ID 生成唯一 ID
function generateUniqueId(serverId) {
  const timestamp = BigInt(Date.now());
  const serverIdBigInt = BigInt(serverId);
  const random = BigInt(Math.floor(Math.random() * 1000)); // 添加一点随机性

  // 组合这些值以创建唯一 ID
  const uniqueId = (timestamp << 20n) + (serverIdBigInt << 10n) + random;
  return uniqueId.toString(); // 以字符串形式返回,便于处理
}

const serverId = 123; // 示例服务器 ID
const id1 = generateUniqueId(serverId);
const id2 = generateUniqueId(serverId);

console.log("唯一 ID 1:", id1);
console.log("唯一 ID 2:", id2);

BigInt 与 JSON

BigInt 值不被 JSON 原生支持。尝试使用 `JSON.stringify()` 序列化包含 BigInt 的 JavaScript 对象将导致 `TypeError`。在处理 JSON 时要处理 BigInt 值,您有几种选择:

  1. 转换为字符串:在序列化之前将 BigInt 转换为字符串。这是最常见和最直接的方法。
  2. 自定义序列化/反序列化:使用自定义的序列化/反序列化函数来处理 BigInt 值。

示例:

转换为字符串:


const data = {
  id: 12345678901234567890n,
  name: "Example Data",
};

// 序列化前将 BigInt 转换为字符串
data.id = data.id.toString();

const jsonData = JSON.stringify(data);
console.log(jsonData); // 输出: {"id":"12345678901234567890","name":"Example Data"}

// 反序列化时,您需要将字符串转换回 BigInt
const parsedData = JSON.parse(jsonData, (key, value) => {
  if (key === "id") {
    return BigInt(value);
  }
  return value;
});

console.log(parsedData.id); // 输出: 12345678901234567890n

自定义序列化/反序列化(使用 `replacer` 和 `reviver`):


const data = {
  id: 12345678901234567890n,
  name: "Example Data",
};

// 自定义序列化
const jsonData = JSON.stringify(data, (key, value) => {
  if (typeof value === 'bigint') {
    return value.toString();
  } else {
    return value;
  }
});

console.log(jsonData);

// 自定义反序列化
const parsedData = JSON.parse(jsonData, (key, value) => {
    if (typeof value === 'string' && /^[0-9]+$/.test(value)) { // 检查它是否为数字字符串
      try {
        return BigInt(value);
      } catch(e) {
          return value;
      }
    }
    return value;
});

console.log(parsedData.id);

浏览器兼容性

BigInt 在现代浏览器中得到了广泛支持。但是,检查旧版浏览器或环境的兼容性至关重要。您可以使用像 Can I use 这样的工具来验证浏览器支持。如果需要支持旧版浏览器,您可以考虑使用 polyfill,但请注意 polyfill 可能会影响性能。

性能考量

虽然 BigInt 提供了一种处理大整数的强大方法,但了解潜在的性能影响也很重要。

因此,仅在必要时使用 BigInt,并且如果您要执行大量 BigInt 运算,请优化您的代码以提高性能。

结论

BigInt 是 JavaScript 的一个宝贵补充,使开发人员能够精确地处理大整数运算。通过了解其特性、限制和用例,您可以利用 BigInt 在各种领域构建健壮且准确的应用程序,包括密码学、金融计算和科学计算。在您的项目中使用 BigInt 时,请记住考虑浏览器兼容性和性能影响。

进一步探索

本指南全面概述了 JavaScript 中的 BigInt。探索链接的资源以获取更深入的信息和高级技术。