作為前端開發,不知道大家是否被大整數困擾過?JavaScript 對大整型一直沒有支援,想要操作大整型數字必須藉助第三方庫,除了麻煩還可能有打包過大和執行時效率的問題。對比 Java 中,早就有了能表示任意精度的BigInteger
。而對於 JavaScript,ECMAScript 中的提案 BigInt
就是一個可以表示任意精度的新的數字原始型別。
本文主要圍繞 BigInt
講講其現狀、特性、進展和目前的使用方法。
Number 型別的侷限性
JavaScript 中的 Number
是雙精度浮點型,這意味著精度有限。Number.MAX_SAFE_INTEGER
就是安全範圍內的最大值,為 2**53-1
。最小安全值為 Number.MIN_SAFE_INTEGER
值為 -((2**53)-1)
。超出安全值的計算都會喪失精度。如下,可以看到 max + 1
與 max + 2
的值相同,這顯然是不對的。
const max = Number.MAX_SAFE_INTEGER; // 9007199254740991
max + 1 // 9007199254740992
max + 2 // 9007199254740992
複製程式碼
至於為什麼最大安全值是 2**53-1
,與 IEEE 754 的 float 浮點數儲存有關,可參考抓住資料的小尾巴 - JS浮點數陷阱及解法。
實際應用中,例如在大整數 ID、高精度時間戳中會導致不安全的問題。Twitter IDs (snowflake)文中說到 Twitter 的 id 生成服務,當 id 持續增長時,就會超出 JS 的安全範圍,因此要求同時冗餘地返回字串型的 id。另一個例子,高精度時間戳在運算的時候也會喪失精度,例如使用 performance
物件與 BigInt
結合,可以獲取精確到皮秒的時間戳(當然這個時間戳是不是真的精準是另一個問題),程式碼如下:
// 1 毫秒(ms) = 1,000 微秒(μs) = 1,000,000 納秒(ns) = 1,000,000,000 皮秒(ps)
const scale = 1000000000
const scaleBig = 1000000000n
const big = BigInt((performance.now() * scale).toFixed(0)) + BigInt(performance.timing.navigationStart) * scaleBig
const normal = (performance.now() + performance.timing.navigationStart) * scale
console.log(big) // 1550488515092440117252n 精確到皮秒
console.log(normal) // 1.550488515092455e+21 精確到微秒
複製程式碼
在沒有 BigInt 的時候,如果想要使用大整型,則不得不借助類似 BigInt 功能的第三方庫。這有可能會影響 JavaScript 程式的效率,比如載入時間、解析時間、編譯時間,以及執行時的效率。下圖為 BigInt
與其他類似第三方庫的效能對比。
BigInt 的特性
BigInt
是一個新的原始型別,可以實現任意精度計算。建立 BigInt
型別的值也非常簡單,只需要在數字後面加上 n
即可。例如,789
變為 789n
。也可以使用全域性方法 BigInt(value)
轉化,入參 value 為數字或數字字串。例如:
BigInt(1234567890) === 1234567890n // true
複製程式碼
另一個例子就是上述的時間戳轉換。
新的原始型別
既然 BigInt
是一個新的原始型別,那麼它就可以使用 typeof
檢測出自己的型別
typeof 111 // "number"
typeof 111n // "bigint"
複製程式碼
同時 BigInt
與 Number
型別的值也是不嚴格相等的。
111 === 111n // false
111 == 111n // true
複製程式碼
在數字布林間的邏輯中,BigInt
與 Number
表現一致。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// → logs 'else', because `0n` is falsy.
複製程式碼
如果算上 BigInt
,JavaScript 中原始型別就從 6 個變為了 7 個。
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (new in ECMAScript 2015)
- BigInt (new in future ECMAScript)
運算
BigInt
支援絕大部分常用運算子,+
, -
, *
, /
, %
, 和 **
。
位運算子 |, &, <<, >>, ^
表現也與 Number
型別中一致。
一元運算子 -
表示負數,但是 +
不能用於表示正數。因為在 webAssembly(asm.js) 中,+x
始終表示一個 Number
或異常情況。
另外就是不能混合使用 BigInt
與 Number
計算,例如下面的結果會丟擲異常:
1 + 1n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
複製程式碼
由於不能混合使用 BigInt
與 Number
,你也不能圖省事將程式碼中所有的 Number
都用 BigInt
代替。需要視情況而定,如果數字有可能變得很大,那麼再決定使用 BigInt
。
API
-
BigInt()
建構函式,類似Number()
,可以將入參轉化為BigInt
型別。BigInt(1) // 1n BigInt(1.5) // RangeError BigInt('1.5') // SyntaxError 複製程式碼
-
BigInt64Array 和 BigUint64Array
同時
BigInt
也可以精確表示64位有符號和無符號整型,所有有兩個新的 TypedArray 即 BigInt64Array 和 BigUint64Array。const view = new BigInt64Array(4); // → [0n, 0n, 0n, 0n] view.length; // → 4 view[0]; // → 0n view[0] = 42n; view[0]; // → 42n 複製程式碼
ECMAScript TC39 進展
目前 ES2019 的新特性都已經確定,見 Twitter -New JavaScript features in ES2019,沒有 BigInt,如下圖:
➡️ Array#{flat,flatMap}
➡️ Object.fromEntries
➡️ String#{trimStart,trimEnd}
➡️ Symbol#description
➡️ try { } catch {} // optional binding
➡️ JSON ⊂ ECMAScript
➡️ well-formed JSON.stringify
➡️ stable Array#sort
➡️ revised Function#toString
複製程式碼
同時可以在 github 上 tc39 已完成的草案中看到。
BigInt
目前處於 Stage 3 階段,問題不大的話,ES2020 中應該被收錄。
支援情況 & PolyFill
目前(201902)瀏覽器支援情況並不理想,只有 Chrome 支援較好,其他瀏覽器支援不好。由於和其他 JavaScript 新特性不同,BigInt 不能很好的被編譯為 ES5。因為 BigInt 中修改了運算子的工作行為,這些行為是不能直接被 polyfill 轉換的。
但是可以使用一個庫 the JSBI library,來實現 BigInt。JSBI 是直接使用了 V8 和 Chrome 中 BigInt 的設計和實現方式,功能與瀏覽器中一致,語法稍有不同:
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 外掛 babel-plugin-transform-jsbi-to-bigint移除 JSBI 轉為原生的 BigInt 語法。例如上述程式碼會被轉為:
const max = BigInt(Number.MAX_SAFE_INTEGER);
const two = 2n;
const result = max + two;
console.log(result);
// → '9007199254740993'
複製程式碼
TypeScript 支援
TypeScript 3.2 已經加入了 BigInt
的型別校驗。將 tsconfig 配置為 target: esnext
即可。用法示例如下:
let foo: bigint = BigInt(100); // the BigInt function
let bar: bigint = 100n; // a BigInt literal
// *Slaps roof of fibonacci function*
// This bad boy returns ints that can get *so* big!
function fibonacci(n: bigint) {
let result = 1n;
for (let last = 0n, i = 0n; i < n; i++) {
const current = result;
result += last;
last = current;
}
return result;
}
fibonacci(10000n)
複製程式碼
小結
如果你確定你的頁面只跑在最新的 Chrome 中,那麼現在就可以大膽的使用 BigInt
了,更優雅高效的處理大資料。若在其他瀏覽器中需要支援,可以使用 JSBI 這個庫,日後甩掉它的姿勢也十分優雅。
看著 JavaScript 越來越健壯,甚是欣喜。隨著端計算能力的強大,AI 的發展,說不定很快就能用到這個 BigInt
特性了。