==
) 和不相等 (!=
)===
) 和不相等 (!==
)BigInt
BigInt
作为构造函数和函数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 * 456n56088n
Bigint 是基本类型值。typeof
为它们返回一个新的结果:
> typeof 123n'bigint'
JavaScript 数字在内部表示为分数乘以指数(有关详细信息,请参阅§16.8 “背景:浮点精度”)。因此,如果我们超过最大的*安全整数* 253-1,仍然可以表示*一些*整数,但它们之间存在间隙:
> 2**53 - 2 // safe9007199254740990
> 2**53 - 1 // safe9007199254740991
> 2**53 // unsafe, same as next integer9007199254740992
> 2**53 + 19007199254740992
> 2**53 + 29007199254740994
> 2**53 + 39007199254740996
> 2**53 + 49007199254740996
> 2**53 + 59007199254740996
Bigint 使我们能够突破 53 位的限制:
> 2n**53n9007199254740992n
> 2n**53n + 1n9007199254740993n
> 2n**53n + 2n9007199254740994n
以下是使用 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;
}
}
}
.deepEqual(
assert1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
[2n, 3n, 5n, 7n, 11n]
[; )
与数字字面量一样,bigint 字面量支持多种进制:
123n
0xFFn
0b1101n
0o777n
负 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 + 1TypeError: Cannot mix BigInt and other types, use explicit conversions
此规则的原因是没有通用的方法可以将数字和 bigint 强制转换为通用类型:数字不能表示超过 53 位的 bigint,bigint 不能表示分数。因此,异常会警告我们可能导致意外结果的拼写错误。
例如,以下表达式的结果应该是 9007199254740993n
还是 9007199254740992
?
2**53 + 1n
也不清楚以下表达式的结果应该是什么:
2n**53n * 3.3
二元 +
、二元 -
、*
、**
按预期工作:
> 7n * 3n21n
可以混合使用 bigint 和字符串:
> 6n + ' apples''6 apples'
/
、%
向零舍入(类似于 Math.trunc()
):
> 1n / 2n0n
一元 -
按预期工作:
> -(-64n)64n
不支持对 bigint 使用一元 +
,因为很多代码都依赖它将其操作数强制转换为数字:
> +23nTypeError: Cannot convert a BigInt value to a number
比较运算符 <
、>
、>=
、<=
按预期工作:
> 17n <= 17ntrue
> 3n > -1ntrue
比较 bigint 和数字不会带来任何风险。因此,我们可以混合使用 bigint 和数字:
> 3n > -1true
位运算符将数字解释为 32 位整数。这些整数可以是有符号的,也可以是无符号的。如果它们是有符号的,则整数的负数是其*二进制补码*(将一个整数与其二进制补码相加(忽略溢出)将产生零):
> 2**32-1 >> 0-1
由于这些整数具有固定大小,因此它们的最高位表示它们的符号:
> 2**31 >> 0 // highest bit is 1-2147483648
> 2**31 - 1 >> 0 // highest bit is 02147483647
对于 bigint,位运算符将负号解释为无限的二进制补码,例如:
-1
是 ···111111
(1 无限向左扩展)-2
是 ···111110
-3
是 ···111101
-4
是 ···111100
也就是说,负号更像是一个外部标志,而不是表示为实际的位。
~
)按位非 (~
) 反转所有位:
> ~0b10n-3n
> ~0n-1n
> ~-2n1n
&
、|
、^
)对 bigint 应用二元位运算符类似于对数字应用它们:
> (0b1010n | 0b0111n).toString(2)'1111'
> (0b1010n & 0b0111n).toString(2)'10'
> (0b1010n | -1n).toString(2)'-1'
> (0b1010n & -1n).toString(2)'1010'
<<
和 >>
)bigint 的带符号位移运算符保留数字的符号:
> 2n << 1n4n
> -2n << 1n-4n
> 2n >> 1n1n
> -2n >> 1n-1n
回想一下,-1n
是一个无限向左扩展的 1 序列。这就是为什么将其左移不会改变它的原因:
> -1n >> 20n-1n
>>>
)bigint 没有无符号右移运算符:
> 2n >>> 1nTypeError: BigInts have no unsigned right shift, use >> instead
为什么?无符号右移背后的想法是将零“从左侧”移入。换句话说,假设二进制位数是有限的。
但是,对于 bigint,没有“左侧”,它们的二进制位数是无限扩展的。这对于负数尤其重要。
带符号右移即使在无限位数的情况下也能正常工作,因为它保留了最高位。因此,它可以适用于 bigint。
==
) 和不相等 (!=
)宽松相等 (==
) 和不相等 (!=
) 会强制转换值:
> 0n == falsetrue
> 1n == truetrue
> 123n == 123true
> 123n == '123'true
===
) 和不相等 (!==
)严格相等 (===
) 和不相等 (!==
) 仅在值具有相同类型时才认为它们相等:
> 123n === 123false
> 123n === 123ntrue
BigInt
与数字类似,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}})123n
BigInt.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 位值。
BigInt64Array
BigUint64Array
DataView.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 };
.equal(
assertJSON.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"}';
.deepEqual(
assertJSON.parse(str, bigintReviver),
value: 9007199254740993n }
{ ; )
我的建议
Array.prototype.forEach()
Array.prototype.entries()
所有现有的 Web API 都只返回和接受数字,并且只会根据具体情况升级到 bigint。
可以想象将 number
拆分为 integer
和 double
,但这会给语言增加许多新的复杂性(几个仅限整数的运算符等)。我在 Gist 中概述了后果。
致谢