BigInt
是 JavaScript 中的新數值基本型別,可以用於表示任意精度的整數。有了 BigInt
,即使超過 Number
的安全整數限制,您也能安全地儲存和運算大型整數。本文將介紹一些使用案例,並透過將 JavaScript 中的 BigInt
與 Number
進行比較,說明 Chrome 67 中的新功能。
使用案例 #
任意精度整數為 JavaScript 解鎖許多新的使用案例。
BigInt
使得正確執行整數運算而不會溢位成為可能。這本身就開啟了無數新的可能性。例如,金融科技中經常使用大型數字的數學運算。
大型整數 ID 和 高精度時間戳記 無法安全地表示為 JavaScript 中的 Number
。這 經常 導致 實際錯誤,並導致 JavaScript 開發人員將它們表示為字串。有了 BigInt
,現在可以將這些資料表示為數值。
BigInt
可以構成最終 BigDecimal
實作的基礎。這對於表示具有小數精度的金額,以及準確地對其進行運算(又稱為 0.10 + 0.20 !== 0.30
問題)很有用。
以前,具有這些使用案例的 JavaScript 應用程式必須使用模擬類似 BigInt
功能的使用者空間函式庫。當 BigInt
廣泛可用時,此類應用程式可以放棄這些執行時間相依關係,轉而使用原生 BigInt
。這有助於減少載入時間、剖析時間和編譯時間,最重要的是提供了顯著的執行時間效能改善。
BigInt
實作效能優於熱門的使用者空間函式庫。現狀:Number
#
JavaScript 中的數字以 雙精度浮點數 表示。這表示它們的精度有限。Number.MAX_SAFE_INTEGER
常數給出可以安全遞增的最大整數。其值為 2**53-1
。
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
是 JavaScript 中一個新的數字基本型別,它可以用 任意精度 表示整數。使用 BigInt
,即使超過 Number
的安全整數限制,你也可以安全地儲存和操作大整數。
要建立 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
,我們知道乘法的結果應以 7
結尾(因為 9 * 3 === 27
)。但是,結果以一堆零結尾。這不可能是正確的!讓我們再用 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
當強制轉換為布林值(例如在使用 if
、&&
、||
或 Boolean(int)
時),BigInt
會遵循與 Number
相同的邏輯。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// → logs 'else', because `0n` is falsy.
運算子 #
BigInt
支援最常見的運算子。二元 +
、-
、*
和 **
都能如預期般運作。/
和 %
也能運作,並會視需要四捨五入為零。位元運算 |
、&
、<<
、>>
和 ^
會執行位元運算,假設負值採用 二補數表示法,就像它們對 Number
所做的那樣。
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
// → 1
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
// → 1n
一元 -
可用於表示負 BigInt
值,例如 -42n
。一元 +
不 受支援,因為這會中斷預期 +x
永遠會產生 Number
或例外的 asm.js 程式碼。
需要注意的一點是,不允許混合 BigInt
和 Number
之間的運算。這是一件好事,因為任何隱式強制轉換都可能會遺失資訊。考慮以下範例
BigInt(Number.MAX_SAFE_INTEGER) + 2.5;
// → ?? 🤔
結果應該是什麼?這裡沒有好的答案。BigInt
無法表示分數,而 Number
無法表示超過安全整數限制的 BigInt
。因此,混合 BigInt
和 Number
之間的運算會導致 TypeError
例外。
這個規則唯一的例外是比較運算子,例如 ===
(如前所述)、<
和 >=
,因為它們會傳回布林值,因此不會有精確度損失的風險。
1 + 1n;
// → TypeError
123 < 124n;
// → true
由於 BigInt
和 Number
通常不會混用,請避免過度載入或神奇地「升級」現有程式碼,以使用 BigInt
取代 Number
。決定要在這兩個網域中的哪一個進行操作,然後堅持下去。對於可能處理大型整數的新 API,BigInt
是最佳選擇。對於已知在安全整數範圍內的整數值,Number
仍然有意義。
另一點要注意的是 >>>
算子,它執行無符號右移,對於 BigInt
沒有意義,因為它們始終是有符號的。基於這個原因,>>>
不適用於 BigInt
。
API #
有幾個新的 BigInt
專用 API 可用。
全域 BigInt
建構函式類似於 Number
建構函式:它將其引數轉換為 BigInt
(如前所述)。如果轉換失敗,它會擲回 SyntaxError
或 RangeError
例外。
BigInt(123);
// → 123n
BigInt(1.5);
// → RangeError
BigInt('1.5');
// → SyntaxError
這些範例中的第一個會將數字文字傳遞給 BigInt()
。這是一個不好的做法,因為 Number
會遭受精度損失,因此我們可能在 BigInt
轉換發生之前就已經失去精度
BigInt(123456789123456789);
// → 123456789123456784n ❌
基於這個原因,我們建議堅持使用 BigInt
文字符號(帶有 n
字尾),或改為將字串(而不是 Number
!)傳遞給 BigInt()
123456789123456789n;
// → 123456789123456789n ✅
BigInt('123456789123456789');
// → 123456789123456789n ✅
兩個函式庫函式可以將 BigInt
值包裝為有符號或無符號整數,限制在特定位元數。BigInt.asIntN(width, value)
將 BigInt
值包裝為 width
位元二進制有符號整數,而 BigInt.asUintN(width, value)
將 BigInt
值包裝為 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 位元限制執行相同的操作。
Polyfilling 和轉譯 BigInt #
撰寫本文時,BigInt
僅在 Chrome 中受支援。其他瀏覽器正積極實作中。但如果你想在現在使用 BigInt
功能,而不犧牲瀏覽器相容性,該怎麼辦?很高興你問了!答案是……至少可以說是很有趣。
與大多數其他現代 JavaScript 功能不同,BigInt
無法合理地轉譯成 ES5。
BigInt
提議 變更了運算子的行為(例如 +
、>=
等),以在 BigInt
上運作。這些變更無法直接 polyfill,而且也讓使用 Babel 或類似工具將 BigInt
程式碼轉譯成後備程式碼變得不可行(在多數情況下)。原因是,這種轉譯必須將程式中的每個運算子替換為呼叫某個函式的動作,而該函式會對其輸入執行類型檢查,這將造成無法接受的執行時間效能損失。此外,這會大幅增加任何轉譯套件的大小,對下載、剖析和編譯時間造成負面影響。
更可行且更具未來性的解決方案是,使用 JSBI 函式庫撰寫程式碼。JSBI 是 V8 和 Chrome 中 BigInt
實作的 JavaScript 移植,它在設計上與原生 BigInt
功能完全相同。不同的是,它不是依賴語法,而是公開 一個 API
import JSBI from './jsbi.mjs';
const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const two = JSBI.BigInt('2');
const result = JSBI.add(max, two);
console.log(result.toString());
// → '9007199254740993'
一旦 BigInt
在所有你關心的瀏覽器中獲得原生支援,你可以 使用 babel-plugin-transform-jsbi-to-bigint
將你的程式碼轉譯成原生 BigInt
程式碼,並移除 JSBI 相依性。例如,上述範例轉譯成
const max = BigInt(Number.MAX_SAFE_INTEGER);
const two = 2n;
const result = max + two;
console.log(result);
// → '9007199254740993'
進一步閱讀 #
如果你有興趣了解 BigInt
在幕後如何運作(例如它們如何在記憶體中表示,以及如何對它們執行運算),請閱讀我們的 V8 部落格文章,其中包含實作詳細資料。
BigInt
支援 #
- Chrome: 自版本 67 起支援
- Firefox: 自版本 68 起支援
- Safari: 自版本 14 起支援
- Node.js: 自版本 12 起支援
- Babel: 支援