JavaScript 中的表示任意精度的 BigInt

做爲前端開發,不知道你們是否被大整數困擾過?JavaScript 對大整型一直沒有支持,想要操做大整型數字必須藉助第三方庫,除了麻煩還可能有打包過大和運行時效率的問題。對比 Java 中,早就有了能表示任意精度的BigInteger。而對於 JavaScript,ECMAScript 中的提案 BigInt 就是一個能夠表示任意精度的新的數字原始類型html

本文主要圍繞 BigInt 講講其現狀、特性、進展和目前的使用方法。前端

Number 類型的侷限性

JavaScript 中的 Number 是雙精度浮點型,這意味着精度有限。Number.MAX_SAFE_INTEGER 就是安全範圍內的最大值,爲 2**53-1。最小安全值爲 Number.MIN_SAFE_INTEGER 值爲 -((2**53)-1)。超出安全值的計算都會喪失精度。以下,能夠看到 max + 1max + 2 的值相同,這顯然是不對的。git

const max = Number.MAX_SAFE_INTEGER; // 9007199254740991
max + 1 // 9007199254740992
max + 2 // 9007199254740992
複製代碼

至於爲何最大安全值是 2**53-1,與 IEEE 754 的 float 浮點數存儲有關,可參考抓住數據的小尾巴 - JS浮點數陷阱及解法github

實際應用中,例如在大整數 ID、高精度時間戳中會致使不安全的問題。Twitter IDs (snowflake)文中說到 Twitter 的 id 生成服務,當 id 持續增加時,就會超出 JS 的安全範圍,所以要求同時冗餘地返回字符串型的 id。另外一個例子,高精度時間戳在運算的時候也會喪失精度,例如使用 performance 對象與 BigInt 結合,能夠獲取精確到皮秒的時間戳(固然這個時間戳是否是真的精準是另外一個問題),代碼以下:web

// 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 與其餘相似第三方庫的性能對比。typescript

BigInt 與其餘相似第三方庫的性能對比

BigInt 的特性

BigInt 是一個新的原始類型,能夠實現任意精度計算。建立 BigInt 類型的值也很是簡單,只須要在數字後面加上 n 便可。例如,789 變爲 789n。也能夠使用全局方法 BigInt(value) 轉化,入參 value 爲數字或數字字符串。例如:瀏覽器

BigInt(1234567890) === 1234567890n // true
複製代碼

另外一個例子就是上述的時間戳轉換。安全

新的原始類型

既然 BigInt 是一個新的原始類型,那麼它就能夠使用 typeof 檢測出本身的類型bash

typeof 111 // "number"
typeof 111n // "bigint"
複製代碼

同時 BigIntNumber 類型的值也是不嚴格相等的。babel

111 === 111n // false
111 == 111n // true
複製代碼

在數字布爾間的邏輯中,BigIntNumber 表現一致。

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 或異常狀況。

另外就是不能混合使用 BigIntNumber 計算,例以下面的結果會拋出異常:

1 + 1n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
複製代碼

因爲不能混合使用 BigIntNumber,你也不能圖省事將代碼中全部的 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 特性了。

參考

相關文章
相關標籤/搜索