JavaScript的Number类型受限于双精度浮点数,最大安全整数为2的53次方减一,超出此范围计算会丢失精度。BigInt作为新的原始类型,支持任意精度整数运算,从Chrome 67开始支持。本文详解BigInt的创建、运算符、类型比较、混合运算限制及BigInt64Array等API,帮助开发者安全处理超大整数。
JavaScript Number的安全整数限制
JavaScript中的基本数据类型Number采用双精度浮点数格式,其能表示的最大安全整数范围为正负9007199254740991,即2的53次方减一。开发者可在浏览器控制台中分别输入Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER来查看对应的最大和最小安全整数值。
const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991
// 注意:为了便于阅读,我使用下划线作为分隔符将这些数字分组为千位数。数字文字分隔符提案对普通的JavaScript数字文字使用正确。
将该最大值加一,可以得到符合预期的结果:
max + 1;
// → 9_007_199_254_740_992 ✅
然而,如果在此基础上继续增加,结果将无法被JavaScript Number类型完全精确表示:
max + 2;
// → 9_007_199_254_740_992 ❌
此时会发现max+1和max+2的计算结果相同。一旦在JavaScript中获取到该特定值,便无法判断其准确性。对于安全整数范围(即从Number.MIN_SAFE_INTEGER到Number.MAX_SAFE_INTEGER)之外的任何整数计算,均可能存在精度丢失风险。因此,在安全范围内的数字整数值是可靠的选择。
BigInt类型介绍与创建
BigInt
BigInt是JavaScript引入的一种新的原始数据类型,支持以任意精度表示整数。借助BigInt,即使数值超出JavaScript Number的安全整数限制,也能安全地存储和操作大整数。
Chrome 67及以上版本开始支持BigInt,本文所有演示代码均基于Chrome 67环境。
创建BigInt的方法是在数字后添加n后缀,例如将123转换为123n。此外,可以使用全局BigInt(number)函数将Number类型转换为BigInt,即BigInt(123)等价于123n。利用这两种方式,可以解决前文提到的精度问题:
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n ✅
这里将两个Number类型数值相乘:
1234567890123456789 * 123;
// → 151851850485185200000 ❌
观察上述两个数字,末尾分别为9和3,9乘以3应得27,但结果末尾却是000,显然存在错误。改用BigInt进行计算:
1234567890123456789n * 123n;
// → 151851850485185185047n ✅
此次得到了正确的结果。
Number的安全整数限制并不适用于BigInt。因此,使用BigInt可以执行正确的整数运算,无需担心精度丢失。
BigInt是JavaScript语言中的一种原始类型,因此可以使用typeof操作符检测其类型:
typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'
由于BigInt是一个独立的类型,BigInt永远不会等于Number,例如42n不等于42。若要比较BigInt和Number,需在比较前将其中一个转换为另一个的类型,或使用抽象相等运算符(==):
42n === BigInt(42);
// → true
42n == 42;
// → true
当BigInt被强制转换为布尔型(如在if、&&、||语句中,或使用Boolean(int)函数时),其转换逻辑与Number相同。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// → logs 'else', because `0n` is falsy.
BigInt运算符与类型比较
运算符
BigInt支持最常见的二元运算符,包括+、-、*、**、/、%,这些运算均正常工作。位运算符|、&、<<、>>的行为也与Number一致。
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
// → 1
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
// → 1n
一元运算符-可用于表示负值BigInt,例如-42n。但不支持一元运算符+,因为这会破坏asm.js代码,在asm.js中+x总是抛出异常。
另一个重要限制是不允许在BigInt和Number之间进行混合运算。示例如下:
BigInt(Number.MAX_SAFE_INTEGER) + 2.5;
// → ?? 🤔
结果应该是什么?这里没有理想的答案。BigInt无法表示小数,而Number无法表示超出安全整数限制的BigInt数值。因此,BigInt与Number之间的混合操作会引发TypeError异常。
该规则的唯一例外是比较运算符,如===、<、>=等。因为它们返回布尔值,不存在精度损失的风险。
1 + 1n;
// → TypeError
123 < 124n;
// → true
BigInt API与数组类型
API
全局BigInt构造函数与Number构造函数类似,用于将参数转换为BigInt(如前所述)。如果转换失败,它将抛出SyntaxError或RangeError异常。
BigInt(123);
// → 123n
BigInt(1.5);
// → RangeError
BigInt('1.5');
// → SyntaxError
两个库函数BigInt.asIntN(width, value)和BigInt.asUintN(width, value)可将BigInt值封装为有符号或无符号整数,并限制特定位数。例如,BigInt.asIntN(width, value)将BigInt值包装为width位二进制有符号整数,而BigInt.asUintN(width, value)将其包装为width位二进制无符号整数。例如,在执行64位算术时,可使用这些API保持适当的数值范围:
// Highest possible BigInt value that can be represented as a
// signed 64-bit integer.
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max);
→ 9223372036854775807n
BigInt.asIntN(64, max + 1n);
// → -9223372036854775808n
// ^ negative because of overflow
请注意,当传递超过64位整数范围的BigInt值(例如绝对值为63位加1位符号)时,会发生溢出。
BigInt可以精确表示64位有符号和无符号整数,这些类型在其他编程语言中常用。两种新的数组类型BigInt64Array和BigUint64Array能更有效地表示和操作这些值的列表:
const view = new BigInt64Array(4);
// → [0n, 0n, 0n, 0n]
view.length;
// → 4
view[0];
// → 0n
view[0] = 42n;
view[0];
// → 42n
BigInt64Array确保其存储的值为64位有符号整数。
// Highest possible BigInt value that can be represented as a
// signed 64-bit integer.
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
// ^ negative because of overflow
BigUint64Array确保其存储的值为64位无符号整数。