JavaScript 最新数据类型的基本指南:BigInt
已发表: 2022-03-10Number
类型不能安全地表示大于 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_INTEGER
和Number.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
隐式类型转换
因为隐式类型转换可能会丢失信息,所以BigInt
和Number
之间的混合操作是不允许的。 混合大整数和浮点数时,结果值可能无法用BigInt
或Number
准确表示。 考虑以下示例:
(9007199254740992n + 1n) + 0.5
此表达式的结果在BigInt
和Number
的域之外。 带有小数部分的Number
不能准确地转换为BigInt
。 大于 2 53的BigInt
无法准确转换为Number
。
由于此限制,无法使用Number
和BigInt
操作数的混合执行算术运算。 您也不能将BigInt
传递给需要Number
的 Web API 和内置 JavaScript 函数。 尝试这样做会导致TypeError
:
10 + 10n; // → TypeError Math.max(2n, 4n, 6n); // → TypeError
请注意,关系运算符不遵循此规则,如下例所示:
10n > 5; // → true
如果您想使用BigInt
和Number
执行算术计算,您首先需要确定应该在其中完成操作的域。 为此,只需调用Number()
或BigInt()
转换任一操作数:
BigInt(10) + 10n; // → 20n // or 10 + Number(10n); // → 20
在Boolean
上下文中遇到时, BigInt
的处理方式与Number
类似。 换句话说,只要不是0n
, BigInt
就被认为是真实值:
if (5n) { // this code block will be executed } if (0n) { // but this code block won't }
对数组进行排序时, BigInt
和Number
类型之间不会发生隐式类型转换:
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 等等,而无需使用库。
重要的是要记住,不能同时使用Number
和BigInt
操作数来执行算术运算。 您需要通过显式转换任一操作数来确定应在其中完成操作的域。 此外,出于兼容性原因,不允许在BigInt
上使用一元加号 ( +
) 运算符。
你怎么看? 你觉得BigInt
有用吗? 让我们在评论中知道!