BigInt
數據類型是為了讓JavaScript程序能表示超出Number
類型支持的數值范圍。在對大整數進行數學運算時,以任意精度表示整數的能力尤為重要。有了BigInt
,整數溢出將不再是一個問題。
此外,你可以安全地使用高精度時間戳、大整數 ID 等,而不必使用任何變通方法。BigInt
目前處于 stage 3 提案階段。一旦加入到規范中,它將成為JavaScript中的第二種數字數據類型,這將使支持的數據類型總數達到8個:
- Boolean
- Null
- Undefined
- Number
- BigInt
- String
- Symbol
- Object
在本文中,我們將仔細研究BigInt
,并了解它如何幫助克服JavaScript中Number
類型的限制。
問題
對于來自其他語言的程序員來說,JavaScript中缺乏顯式整數類型常常令人困惑。許多編程語言支持多種數字類型,如float、double、integer和bignum,但JavaScript不是這樣。在JavaScript中,所有數字都以雙精度64位浮點格式表示,這是由IEEE 754-2008標準定義的。
在此標準下,無法精確表示的非常大的整數將自動四舍五入。準確地說,JavaScript中的Number
類型只能安全地表示 -9007199254740991 (-(253-1))和 9007199254740991 (253-1)之間的整數。任何超出此范圍的整數值都可能丟失精度。
這個很容易驗證,執行以下代碼:
console.log(9999999999999999); // → 10000000000000000
該整數大于JavaScript可以用 Number
原始類型表示的最大數字。因此,它被舍入了。意外的舍入可能會損害程序的可靠性和安全性。這是另一個例子:
// 注意最后一位
9007199254740992 === 9007199254740993; // → true
JavaScript提供了Number.MAX_SAFE_INTEGER
常量,允許你在JavaScript中快速獲得最大安全整數。類似地,你可以通過使用Number.MIN_SAFE_INTEGER
常量獲得最小安全整數:
const minInt = Number.MIN_SAFE_INTEGER;
console.log(minInt); // → -9007199254740991
console.log(minInt - 5); // → -9007199254740996
// 注意它是如何輸出與上面相同的值的
console.log(minInt - 4); // → -9007199254740996
解決辦法
為了解決這些限制,一些JavaScript開發人員使用String
類型表示大整數。例如,Twitter API在使用JSON響應時向對象添加了一個id的字符串版本。此外,還開發了一些庫,如bignumber.js,以便更容易地處理大整數。
有了 BigInt
,應用程序不再需要一個變通方法或庫來安全地表示Number.MAX_SAFE_INTEGER
和Number.Min_SAFE_INTEGER
之外的整數。現在可以在標準JavaScript中執行對大整數的算術操作,而不會有丟失精度的風險。在第三方庫上使用原生數據類型的好處是更好的運行時性能。
要創建BigInt
,只需將n
附加到整數的末尾。對比一下:
console.log(9007199254740995n); // → 9007199254740995n
console.log(9007199254740995); // → 9007199254740996
或者,你可以調用BigInt()
構造函數:
BigInt("9007199254740995"); // → 9007199254740995n
BigInt
字面量也可以寫成二進制、八進制或十六進制形式:
// 二進制
console.log(0b100000000000000000000000000000000000000000000000000011n);
// → 9007199254740995n
// 十六進制
console.log(0x20000000000003n);
// → 9007199254740995n
// 八進制
console.log(0o400000000000000003n);
// → 9007199254740995n
//注意,不支持舊式八進制語法
console.log(0400000000000000003n);
// → SyntaxError
記住,不能使用嚴格的相等運算符來比較BigInt
和普通數字,因為它們不是同一類型的:
console.log(10n === 10); // → false
console.log(typeof 10n); // → bigint
console.log(typeof 10); // → number
相反,你可以使用相等運算符,它在處理操作數之前執行隱式類型轉換:
console.log(10n == 10); // → true
所有算術運算符都可以在BigInt
上使用,除了一元加號(+
)運算符:
10n + 20n; // → 30n
10n - 20n; // → -10n
+10n; // → TypeError: Cannot convert a BigInt value to a number
-10n; // → -10n
10n * 20n; // → 200n
20n / 10n; // → 2n
23n % 10n; // → 3n
10n ** 3n; // → 1000n
let x = 10n;
++x; // → 11n
--x; // → 10n
不支持一元加號(+
)運算符的原因是,有些程序可能依賴于這樣的結果:+
總是產生Number
類型的值,或者拋出異常。改變+
的行為也會破壞asm.js代碼。
當然,當與BigInt
操作數一起使用時,算術運算符應該返回一個BigInt
值。因此,除法(/
)運算符的結果會自動四舍五入到最接近的整數。例如:
25 / 10; // → 2.5
25n / 10n; // → 2n
隱式類型轉換
因為隱式類型轉換可能丟失信息,所以不允許BigInt
和Number
之間的混合操作。當混合使用大整數和浮點數時,結果值可能無法用BigInt
或Number
準確表示。看看下面的例子:
(9007199254740992n + 1n) + 0.5
這個表達式的結果在BigInt
和Number
的范圍之外。帶有小數部分的Number
不能準確地轉換為BigInt
。大于253的BigInt
不能準確轉換為Number
。
由于這個限制,不能使用Number
和 BigInt
操作數的組合來執行算術運算。你也不能將 BigInt
傳遞給Web API和期望Number
類型參數的內置JavaScript函數。試圖這樣做會導致TypeError
:
10 + 10n; // → TypeError
Math.max(2n, 4n, 6n); // → TypeError
注意,關系運算符不遵循此規則,如下例所示:
10n > 5; // → true
如果希望使用BigInt
和 Number
執行算術計算,首先需要確定應該在哪個域中執行操作。為此,只需通過調用Number()
或BigInt()
來轉換操作數:
BigInt(10) + 10n; // → 20n
// or
10 + Number(10n); // → 20
當遇到Boolean
上下文時,BigInt
被視為類似于Number
。換句話說,只要不是0n
, BigInt
就被認為是一個布爾真值:
if (5n) {
// 這個代碼塊將被執行
}
if (0n) {
// 但這個不會
}
對BigInt
和 Number
進行排序時,不會發生隱式類型轉換:
const arr = [3n, 4, 2, 1n, 0, -1n];
arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]
按位運算符如|
, &
, <<
, >>
和 ^
操作 BigInt
與Number
類似。負數被解釋為無窮長二進制補碼。不允許混合操作數。以下是一些例子:
90 | 115; // → 123
90n | 115n; // → 123n
90n | 115; // → TypeError
BigInt 構造函數
與其他基本類型一樣,可以使用構造函數創建BigInt
。如果可能,傳遞給BigInt
的參數會自動轉換為BigInt
:
BigInt("10"); // → 10n
BigInt(10); // → 10n
BigInt(true); // → 1n
無法轉換的數據類型和值會拋出異常:
BigInt(10.2); // → RangeError
BigInt(null); // → TypeError
BigInt("abc"); // → SyntaxError
你可以直接對通過BigInt
構造函數創建的變量執行算術運算:
BigInt(10) * 10n; // → 100n
當用作嚴格相等運算符的操作數時,使用構造函數創建的BigInt
操作數與常規操作數類似:
BigInt(true) === 1n; // → true
庫函數
JavaScript提供了兩個庫函數來把BigInt
值表示為有符號或無符號整數:
-
BigInt.asUintN(width, BigInt)
: 包裝一個介于 0 和 2width-1之間的BigInt
-
BigInt.asIntN(width, BigInt)
: 包裝一個介于 -2width-1 和2width-1-1之間的BigInt
這些函數在執行64位算術操作時特別有用。這樣你就可以保持在預定的范圍內。
瀏覽器支持及轉換
在撰寫本文時,Chrome +67和Opera +54完全支持BigInt
數據類型。不幸的是,Edge和Safari還沒有實現它。火狐默認不支持BigInt
,但可以通過在about:config
中將javascript.options.bigint
設置為true
來啟用。支持的瀏覽器的最新列表可以在Can I use…上找到。
不幸的是,轉換BigInt
是一個極其復雜的過程,這會導致嚴重的運行時性能損失。也不可能直接填充BigInt
,因為該提議改變了幾個現有操作符的行為。目前,更好的選擇是使用JSBI庫,它是BigInt
建議的純JavaScript 實現。
這個庫提供了一個與內置BigInt
行為完全相同的API。下面是使用JSBI的方法:
import JSBI from './jsbi.mjs';
const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const b2 = JSBI.BigInt('10');
const result = JSBI.add(b1, b2);
console.log(String(result)); // → '9007199254741001'
使用JSBI的一個優點是,一旦瀏覽器支持得到改進,你就不需要重寫代碼了。相反,您可以使用babel plugin將你的JSBI代碼自動編譯成本地的BigInt
代碼。此外,JSBI的性能與內置的BigInt
實現相當。你可以期待更廣泛的瀏覽器支持BigInt
。
結論
BigInt
是一種新的數據類型,用于當整數值大于Number
數據類型支持的范圍時。這種數據類型允許我們安全地對大整數執行算術操作,表示高精度時間戳,使用大整數 ID 等等,而不需要使用庫。
重要的是要記住,不能使用Number
和BigInt
操作數的組合來執行算術運算。你需要通過顯式轉換操作數來確定操作應該在哪個域中執行。此外,出于兼容性的原因,不允許在BigInt
上使用一元加號(+
)操作符。
你覺得怎么樣?你覺得BigInt
有用嗎?歡迎評論!
交流
歡迎關注微信公眾號“1024譯站”,同步國際最新互聯網技術資訊。