JavaScript 最新数据类型的基本指南:BigInt

已发表: 2022-03-10
快速总结 ↬在 JavaScript 中, Number类型不能安全地表示大于 2 53的整数值。 这种限制迫使开发人员使用低效的变通方法和第三方库。 BigInt是一种新的数据类型,旨在解决这个问题。

BigInt数据类型旨在使 JavaScript 程序员能够表示大于Number数据类型支持的范围的整数值。 在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。 使用BigInt ,整数溢出将不再是问题。

此外,您可以安全地使用高分辨率时间戳、大整数 ID 等,而无需使用解决方法。 BigInt目前是第 3 阶段的提案。 一旦添加到规范中,它将成为 JavaScript 中的第二种数字数据类型,这将使​​支持的数据类型总数达到八种:

  • 布尔值
  • 空值
  • 不明确的
  • 数字
  • 大整数
  • 细绳
  • 象征
  • 目的

在本文中,我们将仔细研究BigInt ,看看它如何帮助克服 JavaScript 中Number类型的限制。

问题

JavaScript 中缺乏明确的整数类型常常让来自其他语言的程序员感到困惑。 许多编程语言支持多种数字类型,例如 float、double、integer 和 bignum,但 JavaScript 并非如此。 在 JavaScript 中,所有数字都以 IEEE 754-2008 标准定义的双精度 64 位浮点格式表示。

跳跃后更多! 继续往下看↓

在此标准下,无法精确表示的非常大的整数会自动四舍五入。 准确地说,JavaScript 中的Number类型只能安全地表示介于 -9007199254740991 (-(2 53 -1)) 和 9007199254740991 (2 53 -1) 之间的整数。 任何超出此范围的整数值都可能会丢失精度。

这可以通过执行以下代码轻松检查:

 console.log(9999999999999999); // → 10000000000000000

这个整数大于 JavaScript 可以用Number原语可靠表示的最大数字。 因此,它是圆形的。 意外的舍入可能会损害程序的可靠性和安全性。 这是另一个例子:

 // notice the last digits 9007199254740992 === 9007199254740993; // → true

JavaScript 提供了Number.MAX_SAFE_INTEGER常量,可以让您快速获取 JavaScript 中的最大安全整数。 同样,您可以使用Number.MIN_SAFE_INTEGER常量获得最小安全整数:

 const minInt = Number.MIN_SAFE_INTEGER; console.log(minInt); // → -9007199254740991 console.log(minInt - 5); // → -9007199254740996 // notice how this outputs the same value as above console.log(minInt - 4); // → -9007199254740996

解决方案

作为解决这些限制的方法,一些 JavaScript 开发人员使用String类型表示大整数。 例如,Twitter API 在使用 JSON 响应时将字符串版本的 ID 添加到对象。 此外,还开发了许多库,例如 bignumber.js,以使处理大整数更容易。

使用BigInt ,应用程序不再需要解决方法或库来安全地表示Number.MAX_SAFE_INTEGERNumber.Min_SAFE_INTEGER之外的整数。 现在可以在标准 JavaScript 中执行对大整数的算术运算,而不会损失精度。 与第三方库相比,使用本机数据类型的额外好处是更好的运行时性能。

要创建BigInt ,只需将n附加到整数的末尾。 比较:

 console.log(9007199254740995n); // → 9007199254740995n console.log(9007199254740995); // → 9007199254740996

或者,您可以调用BigInt()构造函数:

 BigInt("9007199254740995"); // → 9007199254740995n

BigInt文字也可以用二进制、八进制或十六进制表示法编写:

 // binary console.log(0b100000000000000000000000000000000000000000000000000011n); // → 9007199254740995n // hex console.log(0x20000000000003n); // → 9007199254740995n // octal console.log(0o400000000000000003n); // → 9007199254740995n // note that legacy octal syntax is not supported console.log(0400000000000000003n); // → SyntaxError

请记住,您不能使用严格相等运算符将BigInt与常规数字进行比较,因为它们的类型不同:

 console.log(10n === 10); // → false console.log(typeof 10n); // → bigint console.log(typeof 10); // → number

相反,您可以使用相等运算符,它在计算其操作数之前执行隐式类型转换:

 console.log(10n == 10); // → true

除了一元加号 ( + ) 运算符外,所有算术运算符都可以在BigInt上使用:

 10n + 20n; // → 30n 10n - 20n; // → -10n +10n; // → TypeError: Cannot convert a BigInt value to a number -10n; // → -10n 10n * 20n; // → 200n 20n / 10n; // → 2n 23n % 10n; // → 3n 10n ** 3n; // → 1000n let x = 10n; ++x; // → 11n --x; // → 10n

不支持一元加号 ( + ) 运算符的原因是某些程序可能依赖于+始终生成Number或抛出异常的不变量。 更改+的行为也会破坏 asm.js 代码。

自然,当与BigInt操作数一起使用时,算术运算符应返回BigInt值。 因此,除法 ( / ) 运算符的结果会被自动截断。 例如:

 25 / 10; // → 2.5 25n / 10n; // → 2n

隐式类型转换

因为隐式类型转换可能会丢失信息,所以BigIntNumber之间的混合操作是不允许的。 混合大整数和浮点数时,结果值可能无法用BigIntNumber准确表示。 考虑以下示例:

 (9007199254740992n + 1n) + 0.5

此表达式的结果在BigIntNumber的域之外。 带有小数部分的Number不能准确地转换为BigInt 。 大于 2 53BigInt无法准确转换为Number

由于此限制,无法使用NumberBigInt操作数的混合执行算术运算。 您也不能将BigInt传递给需要Number的 Web API 和内置 JavaScript 函数。 尝试这样做会导致TypeError

 10 + 10n; // → TypeError Math.max(2n, 4n, 6n); // → TypeError

请注意,关系运算符不遵循此规则,如下例所示:

 10n > 5; // → true

如果您想使用BigIntNumber执行算术计算,您首先需要确定应该在其中完成操作的域。 为此,只需调用Number()BigInt()转换任一操作数:

 BigInt(10) + 10n; // → 20n // or 10 + Number(10n); // → 20

Boolean上下文中遇到时, BigInt的处理方式与Number类似。 换句话说,只要不是0nBigInt就被认为是真实值:

 if (5n) { // this code block will be executed } if (0n) { // but this code block won't }

对数组进行排序时, BigIntNumber类型之间不会发生隐式类型转换:

 const arr = [3n, 4, 2, 1n, 0, -1n]; arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]

位运算符,例如|&<<>>^以与Number类似的方式对BigInt进行操作。 负数被解释为无限长的二进制补码。 不允许混合操作数。 这里有些例子:

 90 | 115; // → 123 90n | 115n; // → 123n 90n | 115; // → TypeError

BigInt 构造函数

与其他原始类型一样,可以使用构造函数创建BigInt 。 如果可能,传递给BigInt()的参数会自动转换为BigInt

 BigInt("10"); // → 10n BigInt(10); // → 10n BigInt(true); // → 1n

无法转换的数据类型和值会引发异常:

 BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt("abc"); // → SyntaxError

您可以直接对使用构造函数创建的BigInt执行算术运算:

 BigInt(10) * 10n; // → 100n

当用作严格相等运算符的操作数时,使用构造函数创建的BigInt的处理方式与常规操作数类似:

 BigInt(true) === 1n; // → true

库函数

JavaScript 提供了两个库函数用于将BigInt值表示为有符号或无符号整数:

  • BigInt.asUintN(width, BigInt) :将BigInt包装在 0 到 2宽度-1 之间
  • BigInt.asIntN(width, BigInt) :在 -2 width-1和 2 width-1 -1 之间包装BigInt

这些函数在执行 64 位算术运算时特别有用。 这样您就可以保持在预期范围内。

浏览器支持和转译

在撰写本文时,Chrome +67 和 Opera +54 完全支持BigInt数据类型。 不幸的是,Edge 和 Safari 还没有实现它。 Firefox 默认不支持BigInt ,但可以通过在about:config中将javascript.options.bigint设置为true来启用它。 Can I use... 上提供了支持的浏览器的最新列表。

不幸的是,转译BigInt是一个极其复杂的过程,会导致严重的运行时性能损失。 直接 polyfill BigInt也是不可能的,因为该提案改变了几个现有运算符的行为。 目前,更好的选择是使用 JSBI 库,它是BigInt提案的纯 JavaScript 实现。

这个库提供了一个行为与原生BigInt完全相同的 API。 以下是使用 JSBI 的方法:

 import JSBI from './jsbi.mjs'; const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER); const b2 = JSBI.BigInt('10'); const result = JSBI.add(b1, b2); console.log(String(result)); // → '9007199254741001'

使用 JSBI 的一个优点是,一旦浏览器支持得到改进,您就不需要重写代码。 相反,您可以使用 babel 插件将您的 JSBI 代码自动编译为原生BigInt代码。 此外,JSBI 的性能与原生BigInt实现相当。 您可以期待BigInt的更广泛的浏览器支持。

结论

BigInt是一种新的数据类型,用于在整数值大于Number数据类型支持的范围时使用。 这种数据类型允许我们安全地对大整数执行算术运算、表示高分辨率时间戳、使用大整数 ID 等等,而无需使用库。

重要的是要记住,不能同时使用NumberBigInt操作数来执行算术运算。 您需要通过显式转换任一操作数来确定应在其中完成操作的域。 此外,出于兼容性原因,不允许在BigInt上使用一元加号 ( + ) 运算符。

你怎么看? 你觉得BigInt有用吗? 让我们在评论中知道!