本篇介紹 ES2020 (ES11) 提供的 BigInt
。
本文同步發表於 Titangene Blog:JavaScript 之旅 (22):BigInt
「JavaScript 之旅」系列文章發文於:
Number
在過去,只要 JavaScript 提到 primitive 就會想到 String
、Number
、Boolean
,但其實還有在 ES6 提供的 Symbol
,另外在 spec 的定義中還有 Undefined
、Null
。
除了以上這些,還有本篇要介紹的新 primitive:BigInt
。
下面是 spec 對 primitive value 的定義:
Number
只能安全地表示最大為 $2^{53}$ 的整數,但有時會有大整數的需求,例如:
例如:在 JavaScript 竟然 2 ** 53
會等於 2 ** 53 + 1
:
let x = 2 ** 53;
let y = 2 ** 53 + 1;
console.log(x);
// 9007199254740992
console.log(y);
// 9007199254740992
console.log(x === y);
// true
console.log(typeof x);
// number
console.log(typeof y);
// number
很奇怪吧!所以這就是 BigInt
被提出的原因!
為何會有這個現象是跟 IEEE 754 的浮點數定義有關,詳請可參閱 Daniel Ehrenberg 在 JSConf EU 2018 的 演講簡報 和 演講影片。
BigInt
BigInt
是一種可用來表示 arbitrary precision (任意精度,高精度) 的整數,也就是可以表示大於 $2^{53}$ 的整數。
例如:剛剛 2 ** 53
和 2 ** 53 + 1
的例子若改用 BigInt
就會如你預期的不一樣 (本來就不同啊 XD):
let x = 2n ** 53n;
let y = 2n ** 53n + 1n;
console.log(x);
// 9007199254740992n
console.log(y);
// 9007199254740993n
console.log(x === y);
// false
console.log(typeof x);
// bigint
console.log(typeof y);
// bigint
BigInt
的語法和 Number
直接寫整數不太一樣,BigInt
literal (字面值) 是在整數的後面加上 n
後綴:
let x = 9007199254740992n;
console.log(x);
//9007199254740992n
console.log(typeof x);
// bigint
另一種方式是呼叫 constructor 來建立 BigInt
:
let x = BigInt(9007199254740992);
console.log(x);
//9007199254740992n
console.log(typeof x);
// bigint
BigInt
在 +
、-
、*
、**
和 %
的運算上跟 Number
差不多 (這邊說的差不多是指整數小於 $2^{53}$ 的情況下):
let x = 9007199254740992n;
console.log(x + 1n);
// 9007199254740993n
console.log(x - 1n);
// 9007199254740991n
console.log(x * 2n);
// 18014398509481984n
console.log(x * -2n);
// -18014398509481984n
console.log(x ** 2n);
// 81129638414606681695789005144064n
console.log(x % 10n);
// 2n
但在小數運算上跟 Number
不同,因為 BigInt
不是 BigDecimals
,所以會 rounded towards 0,即不保留任何小數 (類似無條件捨去):
console.log(5 / 2);
// 2.5
console.log(5n / 2n);
// 2n
console.log(5 / 3);
// 1.6666666666666667
console.log(5n / 3n);
// 1n
console.log(5 / 4);
// 1.25
console.log(5n / 4n);
// 1n
在 spec 中的 BigInt::divide ( x, y )
就有定義 rounded towards 0:
BigInt
不嚴格等於 (not strictly equal) Number
,例如:
console.log(0n === 0);
// false
console.log(0n == 0);
// true
在 spec 中的「Strict Equality Comparison」有定義:
false
,即不嚴格等於Number
或 BigInt
就會回傳 Type(x)::equal(x, y)
若將上面範例中的 0n
帶入 spec,那 0n
就是 x
,所以代表步驟 2 會去執行 BigInt::equal (x, y)
,spec 的定義如下:只要兩個值的數學整數值相等時,就會回傳 true
,否則回傳 false
但可照常用 >
、<
、>=
和 <=
進行比較,例如:
console.log(3n > 2);
// true
console.log(3n < 2);
// false
console.log(2 < 3n);
// true
console.log(2 > 3n);
// false
console.log(2 < 2n);
// false
console.log(2 > 2n);
// false
console.log(2 <= 2n);
// true
console.log(2 >= 2n);
// true
若將 BigInt
強制轉型成 Boolean
,行為會跟 Number
類似。
例如:if
陳述句
if (0) {
console.log('if')
} else {
console.log('else')
}
// else
if (0n) {
console.log('if')
} else {
console.log('else')
}
// else
if
陳述句會將值進行 ToBoolean()
強制轉型,spec 定義如下:
例如:二元邏輯運算子 (binary logical operator) 的 &&
和 ||
:
console.log(0 || 10);
// 10
console.log(0 && 10);
// 0
console.log(0n || 10n);
// 10n
console.log(0n && 10n);
// 0n
&&
和 ||
會將值進行 ToBoolean()
強制轉型,spec 定義如下:
例如:使用 Boolean()
constructor:
console.log(Boolean(0));
// false
console.log(Boolean(10));
// true
console.log(Boolean(0n));
// false
console.log(Boolean(10n));
// true
Boolean()
constructor 會將值進行 ToBoolean()
強制轉型,spec 定義如下:
例如:邏輯 NOT 運算子 (logical NOT operator) !
:
console.log(!0);
// true
console.log(!10);
// false
console.log(!0n);
// true
console.log(!10n);
// false
!
會將值進行 ToBoolean()
強制轉型,spec 定義如下:
上面說的這些都將值進行 ToBoolean()
強制轉型,下面是 ToBoolean()
的定義:只有 0n
會回傳 false
,其他都回傳 true
(跟 Number
很像對吧?有興趣的可以看 Number
的部份)
所以這就是 0n
會變成 false
的原因!
BigInt
不能和 unary +
運算子一起使用,需先用 Number()
constructor 轉換:
console.log(+1n);
// TypeError: Cannot convert a BigInt value to a number
console.log(Number(1n));
// 1
在 spec 是有定義的,因為 unary +
運算子會進行 ToNumber()
的強制轉型:
但 ToNumber()
只要遇到 BigInt
就會拋出 TypeError
exception:
Number
一起使用不能和 Number
一起使用,否則會拋出 TypeError
:
console.log(1n + 2);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1n * 2);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
若要和 Number
一起使用,請先用 Number()
constructor 強制轉型 (即 explicit conversions) 成 Number
:
console.log(Number(1n) + 2);
// 3
console.log(Number(1n) * 2);
// 2
下面是 +
運算子在 spec 中的定義,其中的步驟 8 和 9 都會將 +
左右邊的兩個運算元進行 ToNumeric()
的強制轉型:
而下面是 ToNumeric()
的定義,可以看到步驟 2,只要值為 BigInt
就直接回傳值,所以不會跑到步驟 3:
步驟 1 的
ToPrimitive()
只會對型別為Object
的值才會有特殊行為,所以本篇會跳過這邊的細節 XD,否則篇幅會更長,在這裡可以簡單當作將BigInt
型別的值傳給ToPrimitive()
會回傳一樣的值,不會有任何強制轉型的行為。
接著回到 +
運算子的步驟 (圖重貼一次):
Number
,另一個為 BigInt
(因為前面的範例是 1n + 2
)TypeError
exception所以這就是 BigInt
不能和 Number
一起使用的原因。
String
進行串接例如:
console.log(1n + '2');
// 12
console.log('2' + 1n);
// 21
BigInt()
constructor 不能傳入 Number
或 String
型別的小數例如:
console.log(BigInt(2.5));
// RangeError: The number 2.5 cannot be converted to a BigInt because it is not an integer
console.log(BigInt('2.5'));
// SyntaxError: Cannot convert 2.5 to a BigInt
下面是 BigInt()
constructor 在 spec 的定義:
ToPrimitive()
強制轉型Number
,則進行 NumberToBigInt(prim)
強制轉型ToBigInt()
強制轉型先來看為何不能傳入 Number
型別的小數?這跟步驟 3 有關,會進行 NumberToBigInt(prim)
強制轉型,spec 的定義如下:
RangeError
exception那為何不能傳入 String
型別的小數?這跟步驟 4 有關,會進行 ToBigInt()
強制轉型,spec 的定義如下:
StringToBigInt()
強制轉型NaN
,會拋出 SyntaxError
exception而下面是 StringToBigInt()
的定義:
Infinity
、小數點或指數NaN
,則回傳 NaN
,否則回傳與 MV 完全對應的 BigInt
,而不是四捨五入成 Number
所以字串會透過 StringToBigInt()
強制轉行成 NaN
,就會拋出 SyntaxError
exception。