For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2020/proposal-bigint.md.
  • 简体中文
  • BigInt S4

    中文标题:BigInt:JavaScript 中的任意精度整数

    提案概览
    提案速览

    该提案引入了 BigInt,这是一种新的原始类型,用于表示超出 JavaScript 安全整数限制(2^53)的任意大整数。它定义了语法、运算符、比较以及与其他类型的交互,并讨论了陷阱和设计目标。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    BigInt:JavaScript 中的任意精度整数

    Daniel Ehrenberg, Igalia。阶段 4

    此提案已完成并已合并到 ECMA262 规范中。参见规范文本此处

    感谢 Brendan Eich、Waldemar Horwat、Jaro Sevcik、Benedikt Meurer、Michael Saboff、Adam Klein、Sarah Groff-Palermo 等人对这项工作的帮助和反馈。

    目录

    1. 它是什么?
    2. 它如何工作?
    3. 陷阱与异常
    4. 关于此提案

    它是什么?

    BigInt 是一种新的原始类型,提供了一种表示大于 253 的整数的方法,253 是 JavaScript 用 Number 原始类型能够可靠表示的最大数字。

    const x = Number.MAX_SAFE_INTEGER;
    // ↪ 9007199254740991,这是 2^53 减 1
    
    const y = x + 1;
    // ↪ 9007199254740992,好的,检查无误
    
    const z = x + 2
    // ↪ 9007199254740992,等等,这跟上面一样!

    在 Daniel 在 JSConfEU 的演讲幻灯片中了解更多关于 JavaScript 中数字表示方式的信息。

    它如何工作?

    以下部分展示了 BigInt 的实际应用。其中许多内容受到 Mathias Bynens 的 BigInt V8 更新 的影响或直接取自该更新,该更新包含比本页更多的细节。

    语法

    通过在整数末尾追加 n 或调用构造函数来创建 BigInt

    
    const theBiggestInt = 9007199254740991n;
    
    const alsoHuge = BigInt(9007199254740991);
    // ↪ 9007199254740991n
    
    const hugeButString = BigInt('9007199254740991');
    // ↪ 9007199254740991n
    

    示例:计算质数

    function isPrime(p) {
      for (let i = 2n; i * i <= p; i++) {
        if (p % i === 0n) return false;
      }
      return true;
    }
    
    // 接受一个 BigInt 作为参数并返回一个 BigInt
    function nthPrime(nth) {
      let maybePrime = 2n;
      let prime = 0n;
      
      while (nth >= 0n) {
        if (isPrime(maybePrime)) {
          nth -= 1n;
          prime = maybePrime;
        }
        maybePrime += 1n;
      }
      
      return prime;
    }
    

    运算符

    你可以对 BigInt 使用 +*-**%,就像对 Number 一样。

    
    const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
    // ↪ 9007199254740991
    
    const maxPlusOne = previousMaxSafe + 1n;
    // ↪ 9007199254740992n
     
    const theFuture = previousMaxSafe + 2n;
    // ↪ 9007199254740993n,现在可以了!
    
    const multi = previousMaxSafe * 2n;
    // ↪ 18014398509481982n
    
    const subtr = multi – 10n;
    // ↪ 18014398509481972n
    
    const mod = multi % 10n;
    // ↪ 2n
    
    const bigN = 2n ** 54n;
    // ↪ 18014398509481984n
    
    bigN * -1n
    // ↪ –18014398509481984n
    

    / 运算符也按预期适用于整数。然而,由于这些是 BigInt 而不是 BigDecimal,此操作将向零舍入,也就是说,它不会返回任何小数位。

    
    const expected = 4n / 2n;
    // ↪ 2n
    
    const rounded = 5n / 2n;
    // ↪ 2n,而不是 2.5n
    

    参见用于按位运算符的高级文档。

    比较

    BigIntNumber 不严格相等,但宽松相等。

    
    0n === 0
    // ↪ false
    
    0n == 0
    // ↪ true
    

    NumberBigInt 可以像往常一样进行比较。

    1n < 2
    // ↪ true
    
    2n > 1
    // ↪ true
    
    2 > 2
    // ↪ false
    
    2n > 2
    // ↪ false
    
    2n >= 2
    // ↪ true

    它们可以混合在数组中并排序。

    
    const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
    // ↪  [4n, 6, -12n, 10, 4, 0, 0n]
    
    mixed.sort();
    // ↪ [-12n, 0, 0n, 10, 4n, 4, 6]

    条件

    BigInt 在转换为 Boolean 时(如 if||&&Boolean!)的行为类似于 Number

    
    if (0n) {
      console.log('Hello from the if!');
    } else {
      console.log('Hello from the else!');
    }
    
    // ↪ "Hello from the else!"
    
    0n || 12n
    // ↪ 12n
    
    0n && 12n
    // ↪ 0n
    
    Boolean(0n)
    // ↪ false
    
    Boolean(12n)
    // ↪ true
    
    !12n
    // ↪ false
    
    !0n
    // ↪ true
    

    其他 API 说明

    BigInt 也可以用于 BigInt64ArrayBigUint64Array 类型化数组 中以表示 64 位整数。

    const view = new BigInt64Array(4);
    // ↪ [0n, 0n, 0n, 0n]
    view.length;
    // ↪ 4
    view[0];
    // ↪ 0n
    view[0] = 42n;
    view[0];
    // ↪ 42n
    
    // 可以表示为有符号 64 位整数的最大 BigInt 值。
    const max = 2n ** (64n - 1n) - 1n;
    view[0] = max;
    view[0];
    // ↪ 9_223_372_036_854_775_807n
    view[0] = max + 1n;
    view[0];
    // ↪ -9_223_372_036_854_775_808n
    //   ^ 因为溢出而为负
    

    更多关于 BigInt 库函数的信息,参见高级部分。

    陷阱与异常

    NumberString 的互操作

    最大的意外可能是 BigInt 不能与 Number 互换操作。相反,会抛出 TypeError。(阅读设计哲学了解更多关于为什么做出这个决定的原因。)

    
    1n + 2
    // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
    
    1n * 2
    // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
    

    BigInt 也不能使用一元 + 转换为 Number。必须使用 Number

    
    +1n
    // ↪ TypeError: Cannot convert a BigInt value to a number
    
    Number(1n)
    // ↪ 1
    

    但是,BigInt 可以 与字符串连接。

    
    1n + '2'
    // ↪ "12"
    
    '2' + 1n
    // ↪ "21"
    

    因此,建议对于只会遇到小于 253 的值的代码,继续使用 Number

    BigInt 保留用于预期出现大值的情况。否则,通过来回转换,你可能会失去你想要保留的精确性。

    const largeFriend = 900719925474099267n;
    const alsoLarge = largeFriend + 2n;
    
    const sendMeTheBiggest = (n, m) => Math.max(Number(n), Number(m));
    
    sendMeTheBiggest(largeFriend, alsoLarge)
    // ↪900719925474099300  // 这不是任何一个参数!

    Number 值保留用于它们为不大于 253 的整数的情况,对于其他情况,建议使用字符串(或 BigInt 字面量)以避免精度损失。

    const badPrecision = BigInt(9007199254740993);
    // ↪9007199254740992n
    
    const goodPrecision = BigInt('9007199254740993');
    // ↪9007199254740993n
    
    const alsoGoodPrecision = 9007199254740993n;
    // ↪9007199254740993n

    舍入

    如上所述,BigInt 仅表示整数。Number 仅能可靠地表示不超过 253 的整数。这意味着除法和转换为 Number 都可能导致舍入。

    
    5n / 2n
    // ↪ 2n
    
    Number(151851850485185185047n)
    // ↪ 151851850485185200000
    

    密码学

    BigInt 上支持的操作不是常数时间的。因此,BigInt 不适合用于密码学

    许多平台提供对密码学的原生支持,例如 webcryptonode crypto

    其他异常

    尝试将小数值转换为 BigInt 时,无论该值表示为 Number 还是 String,都会抛出异常。

    BigInt(1.5)
    // ↪ RangeError: The number 1.5 is not a safe integer and thus cannot be converted to a BigInt
    
    BigInt('1.5')
    // ↪ SyntaxError: Cannot convert 1.5 to a BigInt
    

    Math 库中的操作在与 BigInt 一起使用时将抛出错误,| 运算符也是如此。

    
    Math.round(1n)
    // ↪ TypeError: Cannot convert a BigInt value to a number
    
    Math.max(1n, 10n)
    // ↪ TypeError: Cannot convert a BigInt value to a number
    
    1n|0
    // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
    

    但是,parseIntparseFloat 会将 BigInt 转换为 Number 并在此过程中丢失精度。(这是因为这些函数会丢弃尾随的非数字值——包括 n。)

    
    parseFloat(1234n)
    // ↪1234
    
    parseInt(10n)
    // ↪10
    
    // 精度丢失!
    parseInt(900719925474099267n)
    // ↪900719925474099300

    最后,BigInt 不能序列化为 JSON。但是,有一些库——例如 granola——可以为你处理这个问题。

    const bigObj = {a: BigInt(10n)};
    JSON.stringify(bigObj)
    // ↪TypeError: Do not know how to serialize a BigInt

    使用建议

    强制转换

    由于在 Number 和 BigInt 之间强制转换可能导致精度损失,因此建议仅在合理预期会出现大于 253 的值时使用 BigInt,并且不要在这两种类型之间进行强制转换。

    关于此提案

    动机:为什么我们需要如此大的数字?

    在 JavaScript 编码中,有几种情况会出现大于 253 的整数——既包括需要有符号或无符号 64 位整数的情况,也包括可能希望使用大于 64 位的整数的情况。

    64 位使用案例

    通常,与 JavaScript 交互的其他系统以 64 位整数提供数据,这些数据在转换为 JavaScript Numbers 时会丢失精度。

    这些可能出现在读取某些机器寄存器或线路协议、使用包含由 64 位系统生成的 GUID 的 protobuf 或 JSON 文档时——包括信用卡号或账号等——目前这些在 JavaScript 中必须保持为字符串。(注意,BigInt 不能直接序列化为 JSON。但你可以使用诸如 granola 之类的库将 BigInt 和其他 JS 数据类型序列化和反序列化为 JSON。)

    在 node 中,fs.stat 可能会以 64 位整数提供一些数据,这已经引起了问题

    fs.lstatSync('one.gif').ino
    // ↪ 9851624185071828
    
    fs.lstatSync('two.gif').ino
    // ↪ 9851624185071828,重复,但文件不同!

    最后,64 位整数支持更高分辨率——纳秒!——的时间戳。这些将在时间提案中使用,该提案目前处于 Stage 1。

    大于 64 位的使用案例

    大于 64 位的整数最有可能在进行大型整数数学计算时出现,例如解决 Project Euler 问题或精确几何计算。添加 BigInt 使得满足用户对高级语言整数运算“正确”且不会突然溢出的合理期望成为可能。

    如果这看起来牵强,可以考虑奔腾 FDIV bug 的情况。1994 年,奔腾芯片中的一个 bug 使得浮点值很少——但可能——不精确。它被一位依赖该精度的数学教授发现。

    设计目标,或为什么这样设计?

    以下原则指导了本提案所做的决策。查看 ADVANCED.md 以获取每个原则的更深入讨论。

    在维护用户直觉和保持精度之间找到平衡

    总的来说,本提案旨在以与用户对 JavaScript 工作方式的直觉互补的方式工作。同时,本提案的目标是为语言增加进一步的精度支持。有时这些可能会冲突。

    当出现混乱情况时,本提案倾向于抛出异常,而不是依赖类型强制转换并冒险给出不精确的答案。这就是在将 BigIntNumber 相加以及其他上述异常时抛出 TypeError 的原因:如果我们没有好的答案,最好不给答案。

    有关这些选择的更多讨论,请参见 Axel Rauschmeyer 的提案关于其对 Numbers 影响的进一步讨论。我们最终得出结论,提供 Number 和 BigInt 之间的透明互操作是不切实际的。

    不破坏数学

    所有运算符的语义理想情况下应基于某种数学第一性原理,以匹配开发者的期望。除法和取模运算符基于其他编程语言中整数的约定。

    不破坏 JavaScript 的易用性

    本提案带有内置的运算符重载,以避免 BigInt 变得过于丑陋而难以使用。一个特别的危险是,如果要使用静态方法操作 BigInts,用户可能会将 BigInt 转换为 Number 以便对其使用 + 运算符——这在大多数情况下都能工作,但值不够大时可能失败,因此可能通过测试。通过包含运算符重载,正确地将 BigInts 相加比将它们转换为 Numbers 更短,从而最大限度地减少此 bug 的发生机会。

    不破坏 Web

    本提案不会改变 Numbers 的工作方式。选择 BigInt 这个名字部分是为了避免更通用的 Integer 名称带来的兼容性风险(部分是为了明确它们对于“大”的情况有用)。

    不破坏良好性能

    这里的设计工作与原型实现相结合,以确保提案可以高效实现。

    不破坏未来潜在的值类型扩展

    在向语言添加新原始类型时,重要的是避免赋予它们难以泛化的超级能力。这是 BigInt 避免混合操作数的另一个好理由。

    然而,混合比较是这个原则的一次性例外,是为了支持直觉设计原则。

    不破坏 JavaScript 的一致模型

    本提案添加了一种新的原始类型及其包装器,类似于 Symbol。作为将 BigInts 集成到 JavaScript 规范中的一部分,需要高度严谨地区分规范中出现的三种类型:数学值、BigIntNumber

    提案状态

    此提案目前处于 Stage 4。

    BigInt 已在 Chrome、Node、Firefox 中发布,并在 Safari 中推进中。

    • V8 由 Georg Neis 和 Jakob Kummerow 完成。
    • JSC 由 Caio Lima 和 Robin Morisset 完成。
    • SpiderMonkey 由 Robin Templeton 和 Andy Wingo 完成。

    相关规范提案: