一文搞懂JavaScript 新數據類型BigInt

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_INTEGERNumber.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

隱式類型轉換

因為隱式類型轉換可能丟失信息,所以不允許BigIntNumber之間的混合操作。當混合使用大整數和浮點數時,結果值可能無法用BigIntNumber準確表示。看看下面的例子:

(9007199254740992n + 1n) + 0.5

這個表達式的結果在BigIntNumber的范圍之外。帶有小數部分的Number不能準確地轉換為BigInt。大于253BigInt不能準確轉換為Number

由于這個限制,不能使用NumberBigInt操作數的組合來執行算術運算。你也不能將 BigInt傳遞給Web API和期望Number類型參數的內置JavaScript函數。試圖這樣做會導致TypeError:

10 + 10n;    // → TypeError
Math.max(2n, 4n, 6n);    // → TypeError

注意,關系運算符不遵循此規則,如下例所示:

10n > 5;    // → true

如果希望使用BigIntNumber執行算術計算,首先需要確定應該在哪個域中執行操作。為此,只需通過調用Number()BigInt()來轉換操作數:

BigInt(10) + 10n;    // → 20n
// or
10 + Number(10n);    // → 20

當遇到Boolean上下文時,BigInt被視為類似于Number。換句話說,只要不是0nBigInt就被認為是一個布爾真值:

if (5n) {
    // 這個代碼塊將被執行
}

if (0n) {
    // 但這個不會
}

BigIntNumber進行排序時,不會發生隱式類型轉換:

const arr = [3n, 4, 2, 1n, 0, -1n];

arr.sort();    // → [-1n, 0, 1n, 2, 3n, 4]

按位運算符如|, &, <<, >>^ 操作 BigIntNumber類似。負數被解釋為無窮長二進制補碼。不允許混合操作數。以下是一些例子:

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 等等,而不需要使用庫。

重要的是要記住,不能使用NumberBigInt操作數的組合來執行算術運算。你需要通過顯式轉換操作數來確定操作應該在哪個域中執行。此外,出于兼容性的原因,不允許在BigInt上使用一元加號(+)操作符。

你覺得怎么樣?你覺得BigInt有用嗎?歡迎評論!

交流

歡迎關注微信公眾號“1024譯站”,同步國際最新互聯網技術資訊。


公眾號:1024譯站
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容