大整數 bigint
August 4, 2022在〈與眾不同的資料型態〉談過,在 ES5 以前,JavaScript 沒有整數與浮點數的區別,數值都是 〈IEEE 754 標準 64 位元浮點數〉,型態為 number
,如果只用 number
來表示整數,位元組中可使用的範圍就是 52 個位元的分數部份,最大整數只能表示到 2⁵³,也就是 9007199254740992,然而 9007199254740992 這數字不安全,因為寫下 9007199254740993,結果還是代表著 9007199254740992:
大整數的問題?
為了能表現更大的整數,有些第三方程式庫實現了大整數運算,可用來表示更大的整數,這樣不就好了嗎?若面對的只是數字計算,實現大整數運算的第三方程式庫或許能解決,但問題往往不那麼單純。例如看看前後端資料傳遞的問題,許多文件常提的案例就是 Twitter 的 64 位元 ID,或是高精度時間戳記,前後端溝通時,須以字串方式傳遞、轉換處理。
另外,在面對 WebAssembly、WebGL 時,會有 64 位元整數的整合問題,例如,WebAssembly 支援 i64
,若要與 JavaScript 互通,參數或傳回值就必須合法化(Legalization),參數部份要將 i64
改為兩個 i32
,分別表示 64 位元整數的高低位元組,i64
傳回值的部份也必須想辦法拆成兩部份,對這些有興趣的話,可以進一步參考〈WebAssembly integration with JavaScript BigInt〉。
另外,也有位元運算的問題,若對 JavaScript 的 number
進行位元運算,數字會被當成 32 位元整數,因此 2 ** 32 - 1 >> 0
結果不是 4294967295,而是 -1,因為最高位元是 1,也就是 2 補數表示整數 -1;簡單來說,問題並不只有大整數運算,有興趣可以進一步參考〈Bigints (advanced)〉。
bigint
ES11 以後在數字後接上 n
,可以建立 bigint
,它是基本型態,typeof
的結果是 'bigint'
,如果想將代表整數的 number
或 string
轉換為 bigint
,可以使用 BigInt
函式,如果指定的值不是代表整數,會引發 RangeError
:
> 19007199254740992n
19007199254740992n
> typeof 19007199254740992n
'bigint'
> BigInt(0)
0n
> BigInt('123456789987654321')
123456789987654321n
> BigInt(1.2)
Uncaught:
RangeError: The number 1.2 cannot be converted to a BigInt because it is not an integer
at BigInt (<anonymous>)
> BigInt('1.2')
Uncaught SyntaxError: Cannot convert 1.2 to a BigInt
>
+
、-
、*
、/
、%
、**
等運算子,可以套用在 bigint
,不過記得,bigint
是整數,/
的結果只會得到整數的部份:
> 1n - 1n
0n
> 2n * 3n
6n
> 1n / 3n
0n
> 10n % 3n
1n
> 3n ** 2n
9n
>
bigint
是整號 &
、|
、^
、<<
、>>
位元運算子,然而不能使用 >>>
:
> 2n << 10n
2048n
> 2n >> 10n
0n
> 2n >>> 10n
Uncaught TypeError: BigInts have no unsigned right shift, use >> instead
>
因為 >>>
是無號右移(unsigned right shift),不管值本來最左邊是什麼位元,位移後最左邊位元直接補 0,這意謂著代表某整數值的位元數量是有限的,然而,bigint
的整數沒有上限的概念(當然,有實體記憶體的物理限制),也就是相當於位元組數量無限,也就不支援 >>>
了。
Python 不支援 >>>
也是同樣的道理,因為 Python 的整數是 int
實例,直接支援大整數。
不要想有個整數時,就隨意地使用 bigint
,它的相關運算不是常數時間。
bigint 與 number?
因為 JavaScript 一直以來,都可以藉由 +
,將值轉換為 number
:
> +'234'
234
> +true
1
>
基於相容性,bigint
不支援單元運算子 +
:
> +1n
Uncaught TypeError: Cannot convert a BigInt value to a number
>
有發現方才一連串的示範,都只使用 bigint
嗎?基本上 bigint
與 number
在多個方面,被限制不能混合運算,會引發 TypeError
,隨便舉幾個來看:
> 1 + 2n
Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
> 2n ** 0.5
Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
>
===
與 !==
嚴格比較時要記得,0 與 0n
是不同的;bigint
與 number
可以混合運算的場合,在於不會發生 bigint
與 number
混淆的情況,例如 >
、>=
、<
、<=
,或者是 bigint
被作為布林值的場合,0n
會被作為成立,其他 bigint
的值會被作為不成立,也就是說〈弱型別的代價 – WAT!〉裡談過的 False family 多了 0n
:
> 0n === 0
false
> 10n > 8
true
> 10n && 2n
2n
> 0n && 2n
0n
> 1n && 0
0
> 1n && 0
0
bigint
與 number
幾乎不能混合運算的結果就是,多數既有的程式庫,包含標準程式庫,像是 Math
上的許多函式,都不能與 bigint
搭配:
> Math.pow(10n, 2n)
Uncaught TypeError: Cannot convert a BigInt value to a number
at Math.pow (<anonymous>)
>
有些人會說,有了 bigint
,終於可以不用使用第三方大整數程式庫了,這種說法並不正確,因為 bigint
與 number
幾乎不能混合運算,搭配 bigint
的程式庫還是必須存在的。
事實上,目前有個〈BigInt Math for JavaScript〉提案;在 bigint
規格底定前,其實有過一些讓 bigint
與 number
互操作的討論,不過都被否決了,有興趣可參考〈ES11 的 bigint〉。
如果真的有 number
與 bigint
必須進行運算,可以明確地將代表整數的 number
透過 BigInt
函式轉換為 bigint
;或者明確地將 bigint
以 Number
轉換為 number
,然而要留意整數範圍問題。