==
) 和不等性 (!=
)===
) 和不等性 (!==
)BigInt
BigInt
作為建構函式和函式BigInt.prototype.*
方法BigInt.*
方法在本章中,我們將探討 JavaScript 中的 bigints,它的儲存空間會根據需要而增長和縮減。
在 ECMAScript 2020 之前,JavaScript 處理整數的方式如下
浮點數和整數只有一個類型:64 位元浮點數(IEEE 754 雙精度)。
在底層,大多數 JavaScript 引擎透明地支援整數:如果一個數字沒有小數位數且在特定範圍內,它可以在內部儲存為真正的整數。此表示法稱為 small integer,通常適合 32 位元。例如,V8 引擎的 64 位元版本中 small integer 的範圍為 −231 到 231−1 (來源)。
JavaScript 數字也可以表示超出 small integer 範圍的整數,作為浮點數。在此,安全範圍為正負 53 位元。有關此主題的更多資訊,請參閱 §16.9.3 “安全整數”。
有時,我們需要超過有號 53 位元,例如
Bigint 是整數的新基本資料類型。Bigints 在位元中沒有固定的儲存大小;它們的大小會根據它們所代表的整數而調整
Bigint 文字是一個或多個數字的序列,後綴為 n
,例如
123n
-
和 *
等運算子會重載,並與 bigints 一起使用
> 123n * 456n56088n
Bigints 是基本值。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
Bigints 使我們能夠超過 53 位元
> 2n**53n9007199254740992n
> 2n**53n + 1n9007199254740993n
> 2n**53n + 2n9007199254740994n
以下是使用 bigints 的樣子(程式碼基於提案中的範例)
/**
* 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
&
、|
、^
)將二進制按位元運算子套用於大整數時,其運作方式類似於將其套用於數字
> (0b1010n | 0b0111n).toString(2)'1111'
> (0b1010n & 0b0111n).toString(2)'10'
> (0b1010n | -1n).toString(2)'-1'
> (0b1010n & -1n).toString(2)'1010'
<<
和 >>
)大整數的有號位移運算子會保留數字的符號
> 2n << 1n4n
> -2n << 1n-4n
> 2n >> 1n1n
> -2n >> 1n-1n
請記住,-1n
是一連串向左延伸至無限大的 1。這就是為什麼將其向左位移不會改變它的原因
> -1n >> 20n-1n
>>>
)大整數沒有無號右位移運算子
> 2n >>> 1nTypeError: BigInts have no unsigned right shift, use >> instead
為什麼?無號右位移背後的想法是將一個零從「左邊」移入。換句話說,假設是有有限數量的二進制數字。
然而,對於大整數而言,沒有「左邊」,其二進制數字會無限延伸。這對於負數來說尤其重要。
有號右位移運算子即使在有無限個數字時也能運作,因為最高位元會被保留。因此,它可以調整為大整數。
==
) 和不等 (!=
)寬鬆相等 (==
) 和不等 (!=
) 會強制轉換值
> 0n == falsetrue
> 1n == truetrue
> 123n == 123true
> 123n == '123'true
===
) 和不等 (!==
)嚴格相等 (===
) 和不等 (!==
) 僅在值具有相同類型時才將值視為相等
> 123n === 123false
> 123n === 123ntrue
BigInt
類似於數字,大整數具有相關的包裝器建構函式 BigInt
。
BigInt
作為建構函式和函式new BigInt()
:會擲出 TypeError
。
BigInt(x)
會將任意值 x
轉換為大整數。其運作方式類似於 Number()
,但有幾個差異,這些差異總結於表 13 中,並在以下小節中更詳細地說明。
x |
BigInt(x) |
---|---|
未定義 |
會擲出 TypeError |
null |
會擲出 TypeError |
布林值 | false → 0n ,true → 1n |
數字 | 範例:123 → 123n |
非整數 → 會擲出 RangeError |
|
大整數 | x (不變更) |
字串 | 範例:'123' → 123n |
無法解析 → 會擲出 SyntaxError |
|
符號 | 會擲出 TypeError |
物件 | 可設定(例如透過 .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(0n) → false |
!0n → true |
Boolean(int) → true |
!int → false |
|
數字 | Number(7n) → 7 (範例) |
+int → TypeError (1) |
字串 | String(7n) → '7' (範例) |
''+7n → '7' (範例) |
註腳
+
,因為許多程式碼依賴它將其運算元轉換為數字。拜 bigint 所賜,TypedArray 和 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()
所有現有的網路 API 只會傳回和接受數字,而且只會逐案升級到大整數。
可以想見,可以將 number
分割成 integer
和 double
,但這會為語言增加許多新的複雜性(幾個僅限整數的運算子等)。我在 Gist 中勾勒出後果。
致謝