==) 和不相等 (!=)===) 和不相等 (!==)BigIntBigInt 作为构造函数和函数BigInt.prototype.* 方法BigInt.* 方法在本章中,我们将介绍 bigint,它是 JavaScript 中的一种整数类型,其存储空间会根据需要增长和缩减。
在 ECMAScript 2020 之前,JavaScript 如下处理整数:
只有一种类型用于表示浮点数和整数:64 位浮点数(IEEE 754 双精度)。
在底层,大多数 JavaScript 引擎都透明地支持整数:如果一个数字没有小数部分并且在一定范围内,它可以在内部存储为真正的整数。这种表示形式称为*小整数*,通常适合 32 位。例如,64 位版本的 V8 引擎上的小整数范围是 -231 到 231-1(来源)。
JavaScript 数字也可以使用浮点数表示超出小整数范围的整数。在这里,安全范围是正负 53 位。有关此主题的更多信息,请参阅§16.9.3 “安全整数”。
有时,我们需要超过有符号 53 位的精度,例如:
*Bigint* 是一种新的整数基本数据类型。Bigint 没有固定的位存储大小;它们的大小会根据它们表示的整数进行调整:
bigint 字面量是一个或多个数字序列,后缀为 n,例如:
123n运算符(如 - 和 *)已重载,可用于 bigint:
> 123n * 456n
56088nBigint 是基本类型值。typeof 为它们返回一个新的结果:
> typeof 123n
'bigint'JavaScript 数字在内部表示为分数乘以指数(有关详细信息,请参阅§16.8 “背景:浮点精度”)。因此,如果我们超过最大的*安全整数* 253-1,仍然可以表示*一些*整数,但它们之间存在间隙:
> 2**53 - 2 // safe
9007199254740990
> 2**53 - 1 // safe
9007199254740991
> 2**53 // unsafe, same as next integer
9007199254740992
> 2**53 + 1
9007199254740992
> 2**53 + 2
9007199254740994
> 2**53 + 3
9007199254740996
> 2**53 + 4
9007199254740996
> 2**53 + 5
9007199254740996Bigint 使我们能够突破 53 位的限制:
> 2n**53n
9007199254740992n
> 2n**53n + 1n
9007199254740993n
> 2n**53n + 2n
9007199254740994n以下是使用 bigint 的示例(代码基于提案中的示例):
/**
* Takes a bigint as an argument and returns a bigint
*/
function nthPrime(nth) {
if (typeof nth !== 'bigint') {
throw new TypeError();
}
function isPrime(p) {
for (let i = 2n; i < p; i++) {
if (p % i === 0n) return false;
}
return true;
}
for (let i = 2n; ; i++) {
if (isPrime(i)) {
if (--nth === 0n) return i;
}
}
}
assert.deepEqual(
[1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
[2n, 3n, 5n, 7n, 11n]
);与数字字面量一样,bigint 字面量支持多种进制:
123n0xFFn0b1101n0o777n负 bigint 通过在前面加上一元减号运算符来生成:-0123n
_) 作为分隔符 [ES2021]与数字字面量一样,我们可以在 bigint 字面量中使用下划线 (_) 作为分隔符:
const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;Bigint 通常用于金融技术领域表示货币。分隔符在这里也很有帮助:
const priceInCents = 123_000_00n; // 123 thousand dollars与数字字面量一样,适用两条限制:
对于大多数运算符,我们不允许混合使用 bigint 和数字。如果这样做,则会抛出异常:
> 2n + 1
TypeError: Cannot mix BigInt and other types, use explicit conversions此规则的原因是没有通用的方法可以将数字和 bigint 强制转换为通用类型:数字不能表示超过 53 位的 bigint,bigint 不能表示分数。因此,异常会警告我们可能导致意外结果的拼写错误。
例如,以下表达式的结果应该是 9007199254740993n 还是 9007199254740992?
2**53 + 1n也不清楚以下表达式的结果应该是什么:
2n**53n * 3.3二元 +、二元 -、*、** 按预期工作:
> 7n * 3n
21n可以混合使用 bigint 和字符串:
> 6n + ' apples'
'6 apples'/、% 向零舍入(类似于 Math.trunc()):
> 1n / 2n
0n一元 - 按预期工作:
> -(-64n)
64n不支持对 bigint 使用一元 +,因为很多代码都依赖它将其操作数强制转换为数字:
> +23n
TypeError: Cannot convert a BigInt value to a number比较运算符 <、>、>=、<= 按预期工作:
> 17n <= 17n
true
> 3n > -1n
true比较 bigint 和数字不会带来任何风险。因此,我们可以混合使用 bigint 和数字:
> 3n > -1
true位运算符将数字解释为 32 位整数。这些整数可以是有符号的,也可以是无符号的。如果它们是有符号的,则整数的负数是其*二进制补码*(将一个整数与其二进制补码相加(忽略溢出)将产生零):
> 2**32-1 >> 0
-1由于这些整数具有固定大小,因此它们的最高位表示它们的符号:
> 2**31 >> 0 // highest bit is 1
-2147483648
> 2**31 - 1 >> 0 // highest bit is 0
2147483647对于 bigint,位运算符将负号解释为无限的二进制补码,例如:
-1 是 ···111111(1 无限向左扩展)-2 是 ···111110-3 是 ···111101-4 是 ···111100也就是说,负号更像是一个外部标志,而不是表示为实际的位。
~)按位非 (~) 反转所有位:
> ~0b10n
-3n
> ~0n
-1n
> ~-2n
1n&、|、^)对 bigint 应用二元位运算符类似于对数字应用它们:
> (0b1010n | 0b0111n).toString(2)
'1111'
> (0b1010n & 0b0111n).toString(2)
'10'
> (0b1010n | -1n).toString(2)
'-1'
> (0b1010n & -1n).toString(2)
'1010'<< 和 >>)bigint 的带符号位移运算符保留数字的符号:
> 2n << 1n
4n
> -2n << 1n
-4n
> 2n >> 1n
1n
> -2n >> 1n
-1n回想一下,-1n 是一个无限向左扩展的 1 序列。这就是为什么将其左移不会改变它的原因:
> -1n >> 20n
-1n>>>)bigint 没有无符号右移运算符:
> 2n >>> 1n
TypeError: BigInts have no unsigned right shift, use >> instead为什么?无符号右移背后的想法是将零“从左侧”移入。换句话说,假设二进制位数是有限的。
但是,对于 bigint,没有“左侧”,它们的二进制位数是无限扩展的。这对于负数尤其重要。
带符号右移即使在无限位数的情况下也能正常工作,因为它保留了最高位。因此,它可以适用于 bigint。
==) 和不相等 (!=)宽松相等 (==) 和不相等 (!=) 会强制转换值:
> 0n == false
true
> 1n == true
true
> 123n == 123
true
> 123n == '123'
true===) 和不相等 (!==)严格相等 (===) 和不相等 (!==) 仅在值具有相同类型时才认为它们相等:
> 123n === 123
false
> 123n === 123n
trueBigInt与数字类似,bigint 也有关联的包装器构造函数 BigInt。
BigInt 作为构造函数和函数new BigInt():抛出 TypeError。
BigInt(x) 将任意值 x 转换为 bigint。这与 Number() 类似,但有一些区别,这些区别总结在表 13 中,并在以下小节中进行了更详细的解释。
x |
BigInt(x) |
|---|---|
undefined |
抛出 TypeError |
null |
抛出 TypeError |
| boolean | false → 0n, true → 1n |
| number | 示例:123 → 123n |
非整数 → 抛出 RangeError |
|
| bigint | x(无变化) |
| string | 示例:'123' → 123n |
无法解析 → 抛出 SyntaxError |
|
| symbol | 抛出 TypeError |
| object | 可配置(例如,通过 .valueOf()) |
undefined 和 null如果 x 是 undefined 或 null,则会抛出 TypeError:
> BigInt(undefined)
TypeError: Cannot convert undefined to a BigInt
> BigInt(null)
TypeError: Cannot convert null to a BigInt如果字符串不表示整数,则 BigInt() 会抛出 SyntaxError(而 Number() 返回错误值 NaN):
> BigInt('abc')
SyntaxError: Cannot convert abc to a BigInt不允许使用后缀 'n':
> BigInt('123n')
SyntaxError: Cannot convert 123n to a BigInt允许使用 bigint 字面量的所有进制:
> BigInt('123')
123n
> BigInt('0xFF')
255n
> BigInt('0b1101')
13n
> BigInt('0o777')
511n> BigInt(123.45)
RangeError: The number 123.45 cannot be converted to a BigInt because
it is not an integer
> BigInt(123)
123n如何将对象转换为 bigint 是可以配置的,例如,通过覆盖 .valueOf():
> BigInt({valueOf() {return 123n}})
123nBigInt.prototype.* 方法BigInt.prototype 包含基本 bigint“继承”的方法:
BigInt.prototype.toLocaleString(locales?, options?)BigInt.prototype.toString(radix?)BigInt.prototype.valueOf()BigInt.* 方法BigInt.asIntN(width, theInt)
将 theInt 转换为 width 位(有符号)。这会影响该值在内部的表示方式。
BigInt.asUintN(width, theInt)
将 theInt 转换为 width 位(无符号)。
类型转换允许我们创建具有特定位数的整数值。如果我们想将自己限制为仅使用 64 位整数,则必须始终进行类型转换:
const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);下表显示了将 bigint 转换为其他基本类型时会发生什么:
| 转换为 | 显式转换 | 强制转换(隐式转换) |
|---|---|---|
| boolean | Boolean(0n) → false |
!0n → true |
Boolean(int) → true |
!int → false |
|
| number | Number(7n) → 7(示例) |
+int → TypeError (1) |
| string | String(7n) → '7'(示例) |
''+7n → '7'(示例) |
脚注
+,因为很多代码都依赖它将其操作数强制转换为数字。由于 bigint 的出现,类型化数组和 DataView 现在可以支持 64 位值。
BigInt64ArrayBigUint64ArrayDataView.prototype.getBigInt64()DataView.prototype.setBigInt64()DataView.prototype.getBigUint64()DataView.prototype.setBigUint64()JSON 标准是固定的,不会改变。好处是旧的 JSON 解析代码永远不会过时。缺点是 JSON 不能扩展为包含 bigint。
将 bigint 字符串化会抛出异常
> JSON.stringify(123n)
TypeError: Do not know how to serialize a BigInt
> JSON.stringify([123n])
TypeError: Do not know how to serialize a BigInt因此,我们最好的选择是将 bigint 存储在字符串中
const bigintPrefix = '[[bigint]]';
function bigintReplacer(_key, value) {
if (typeof value === 'bigint') {
return bigintPrefix + value;
}
return value;
}
const data = { value: 9007199254740993n };
assert.equal(
JSON.stringify(data, bigintReplacer),
'{"value":"[[bigint]]9007199254740993"}'
);以下代码展示了如何解析字符串,例如我们在上一个示例中生成的字符串。
function bigintReviver(_key, value) {
if (typeof value === 'string' && value.startsWith(bigintPrefix)) {
return BigInt(value.slice(bigintPrefix.length));
}
return value;
}
const str = '{"value":"[[bigint]]9007199254740993"}';
assert.deepEqual(
JSON.parse(str, bigintReviver),
{ value: 9007199254740993n }
);我的建议
Array.prototype.forEach()Array.prototype.entries()所有现有的 Web API 都只返回和接受数字,并且只会根据具体情况升级到 bigint。
可以想象将 number 拆分为 integer 和 double,但这会给语言增加许多新的复杂性(几个仅限整数的运算符等)。我在 Gist 中概述了后果。
致谢