JavaScriptのBigIntを勉強してみた

JavaScriptのNumber型で正確に表せる範囲は、最大で `9007199254740991` (`2^53 − 1`)、最小で `−9007199254740991` (`−(2^53 − 1)`)

個人的に読みにくいので日本語で読みやすく書くと、`9007兆1992億5474万0991` が最大です

最大値、最小値はそれぞれ、`Number.MAX_SAFE_INTEGER`、`Number.MIN_SAFE_INTEGER`で定義されています。

```js
console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
```

この範囲外の数字だとJavaScritptでは正しく扱えない場合があります

参考：[Number.MAX_SAFE_INTEGER - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)

```js
const x = Number.MAX_SAFE_INTEGER + 1;
const y = Number.MAX_SAFE_INTEGER + 2;
console.log(x === y); // true

console.log(9007199254740992 === 9007199254740993) // true

console.log(9007199254740992 < 9007199254740993) // false
```

↑のように`Number.MAX_SAFE_INTEGER`より大きい数字はうまく動いてくれません。なのでBigIntを使うことで正しく扱うことができます。

# BigIntとは

仕様

- [tc39/proposal-bigint](https://github.com/tc39/proposal-bigint)
- [ECMAScript® 2020 Language Specification](https://tc39.es/ecma262/)

`2^53`より大きい値の整数を表せるデータ型

`String`、`Number`、`null`、`undefind`、`Symbol`などと同じく`BigInt`もデータ型の一つ

---

私はあまり詳しくないのですが、こちらのcommitによると、2019の9月に stage 4 となり仕様が確定したJavaScriptの新しい機能です。
https://github.com/tc39/proposal-bigint/commit/771df8ff3516ea5f3397153d17d04e6c6b75a4ce

# 構文

整数の末尾に`n`を付けるか、`BigInt(数字)` とする

```js
// 末尾にnをつける
console.log(9007199254740991n);

// BigIntを呼び出す
console.log(BigInt(9007199254740991));

// 文字列でもOK
console.log(BigInt("9007199254740991"));

// 16進数でもOK
console.log(0x1fffffffffffffn);
console.log(BigInt(0x1fffffffffffff));

// 8進数でもOK
console.log(0o377777777777777777n);
console.log(BigInt(0o377777777777777777));

// 2進数でもOK
console.log(0b11111111111111111111111111111111111111111111111111111n);
console.log(BigInt(0b11111111111111111111111111111111111111111111111111111));
```

![Screen Shot 2019-11-05 at 22.58.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/2ce8566e-d516-7a29-0efe-78b7f8c01172.png)

### `typeof`は`'bigint'`を返す

```js
console.log(typeof 1n); // 'bigint'
console.log(typeof BigInt(1)); // 'bigint'

console.log(typeof 1n === 'bigint'); // true
console.log(typeof BigInt(1) === 'bigint'); // true
```

# `Number.MAX_SAFE_INTEGER`以上の値で試してみる

```js
// Numberで正確に表せる最大値
console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991

console.log(9007199254740992 === 9007199254740993) // true
console.log(9007199254740992n === 9007199254740993n) // false

console.log(9007199254740992 < 9007199254740993) // false
console.log(9007199254740992n < 9007199254740993n) // true

// 最小より小さい値でも確認
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991

console.log(-9007199254740992 === -9007199254740993) // true
console.log(-9007199254740992n === -9007199254740993n) // false

console.log(-9007199254740992 > -9007199254740993) // false
console.log(-9007199254740992n > -9007199254740993n) // true
```

BigIntを使うことで桁が大きい値でも正しく扱うことができました。

# 四則演算

```js
const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);

console.log(previousMaxSafe + 1n); // 9007199254740992n

console.log(previousMaxSafe * 2n); // 18014398509481982n

console.log(previousMaxSafe - 1n); // 9007199254740990n

console.log(4n / 2n); // 2n
// 結果が小数点となる場合は切り捨てられる
console.log(5n / 2n); // 2n 
// 切り捨ては0に向かって丸め込まれるようです
console.log(-5n / 2n); // -2n 

console.log(100n % 3n); // 1n

console.log(2n ** 3n); // 8n

console.log(+1n); // TypeError: Cannot convert a BigInt value to a number
console.log(-1n); // -1n

let max = Number.MAX_SAFE_INTEGER;
let min = Number.MIN_SAFE_INTEGER;
max++;
min--;
console.log(max) // 9007199254740992
console.log(min) // -9007199254740992
```

# 真偽値に変換

Numberと同じように扱える

```js
console.log(Boolean(0n)) // false
console.log(Boolean(1n)) // true

if (1n) {
    // 実行される
}

if (0n) {
    // 実行されない
}
```

# `BigInt`を`===`、`==`で試してみた

```js
console.log(10n === 10) // false
console.log(10n == 10) // true

console.log(10n === "10") // false
console.log(10n == "10") // true

console.log(1n === true) // false
console.log(1n == true) // true
```

# `Number`と`BigInt`で四則演算

```js
1 + 1n // TypeError: Cannot mix BigInt and other types, use explicit conversions
```

明示的に型変換しないとエラーになってしまう。

# `Number`と`BigInt`で比較演算

`Number`と`BigInt`は通常どおりに比較できます

```js
console.log(1n < 2) // true

console.log(2n > 1) // true

console.log(2n > 2) // false

console.log(2n >= 2) // true
```

# sort

`Number`と`BigInt`が混在していてもソートできる

```js
const mixed = [4n, 6, -12n, 10, 4, 0, 0n];

console.log(mixed.sort()); // [-12n, 0, 0n, 10, 4n, 4, 6]
```

※辞書順比較だと問題ないですが、コールバック関数で四則演算などした場合はエラーになりますので、型変換などしてソートを行うと良いと思います

@le_panda_noir さんからのコメント参考

```js
mixed.sort((a, b) => a - b); // TypeError
```

# `Math`オブジェクトのメソッドでも使用できない

```js
Math.round(1n) // TypeError: Cannot convert a BigInt value to a number

Math.max(1n, 10n) // TypeError: Cannot convert a BigInt value to a number
```

# 小数点

整数でないとBigIntは使えません

```js
console.log(BigInt(1.5)) // RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer

console.log(BigInt('1.5')) // SyntaxError: Cannot convert 1.5 to a BigInt
```

# Static methods

BigIntは2つのstatic関数を持っています

### `BigInt.asIntN(width, bigint)`

第２引数で指定したbigintを `-2 ** (width - 1)` 〜 `2 ** (width - 1) - 1` の範囲内で収まる、符号付きのBigIntの値を返します

```js
// 64bitの範囲 -9223372036854775808 〜 9223372036854775807
const max = 2n ** (64n - 1n) - 1n; // 9223372036854775807n
console.log(BigInt.asIntN(64, max)); // 9223372036854775807n

// 溢れた場合
console.log(BigInt.asIntN(64, max + 1n)); // -9223372036854775808n
console.log(BigInt.asIntN(64, max + 2n)); // -9223372036854775807n
```

### `BigInt.asUintN(width, bigint)`

第２引数で指定したbigintを `0` 〜 `2 ** width - 1` の範囲内で収まる、符号なしのBigIntの値を返します

```js
// 64bitの範囲(符号なし) 0 〜 18446744073709551615
const max = 2n ** 64n - 1n; // 18446744073709551615n
console.log(BigInt.asUintN(64, max)); // 18446744073709551615n

// 溢れた場合
console.log(BigInt.asUintN(64, max + 1n)); // 0n
console.log(BigInt.asUintN(64, max + 2n)); // 1n
```

# `JSON.stringify` でうまく使えない

```js
console.log(JSON.stringify({ a: 1n })) // TypeError: Do not know how to serialize a BigInt
```

`toJSON` 上書きすると、使えはするそうです

```js
BigInt.prototype.toJSON = function() { return this.toString(); }
console.log(JSON.stringify({ a: 1n })) // '{"a":"1"}'
```

# ブラウザサポート

https://caniuse.com/#feat=bigint

使えないブラウザもいくつかあります

※2019/11/17確認

<img width="1390" alt="Screen Shot 2019-11-17 at 19.45.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/c9ca823d-ec79-0f6a-ac59-402efe225515.png">

### ブラウザ対応

`JSBI`というライブラリを使うのが良いようです。

https://github.com/GoogleChromeLabs/jsbi

```bash
yarn add jsbi
```

node 12.12.0 で実行してみたサンプル

```js:index.mjs
import JSBI from 'jsbi';

const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
console.log(String(max));
// → '9007199254740991'
const other = JSBI.BigInt('2');
const result = JSBI.add(max, other);
console.log(String(result));
// → '9007199254740993'
```

```console
$ node --experimental-modules index.mjs 
(node:9580) ExperimentalWarning: The ESM module loader is experimental.
9007199254740991
9007199254740993
```

---

`JSBI`を使っていれば、`babel-plugin-transform-jsbi-to-bigint` を使うことで
`JSBI`を`BigInt`の構文にトランスパイルすることができます。

https://github.com/GoogleChromeLabs/babel-plugin-transform-jsbi-to-bigint

なのでIEやSafariなどの`BigInt`がまだサポートされていないブラウザでも使いたい場合は、まず`JSBI`で`BigInt`の処理を実装します。
それらのブラウザで`BigInt`がサポートされる日が来たら、babelで`JSBI`をネイティブの`BigInt`にトランスパイルします。
そうすることで書いたコードを変更することなく、ブラウザはネイティブの`BigInt`を使用してくれます。

```bash
yarn add jsbi
yarn add -D @babel/core @babel/cli babel-plugin-transform-jsbi-to-bigint
```

このように、`JSBI`のコードをネイティブの`BigInt`に変換することができました

```js:index.js
import JSBI from 'jsbi';

const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
console.log(String(max));
// → '9007199254740991'
const other = JSBI.BigInt('2');
const result = JSBI.add(max, other);
console.log(String(result));
// → '9007199254740993'
```

```console
$ yarn babel --plugins transform-jsbi-to-bigint index.js 
/* 省略 */
const max = BigInt(Number.MAX_SAFE_INTEGER);
console.log(String(max)); // → '9007199254740991'

const other = 2n;
const result = max + other;
console.log(String(result)); // → '9007199254740993'

✨  Done in 0.40s.
```

# 参考

- [tc39/proposal-bigint](https://github.com/tc39/proposal-bigint)
- [he Essential Guide To JavaScript’s Newest Data Type: BigInt — Smashing Magazine](https://www.smashingmagazine.com/2019/07/essential-guide-javascript-newest-data-type-bigint/)
- [JavaScriptのBigIntで任意精度の整数値を扱う](https://sbfl.net/blog/2018/06/18/javascript-bigint/)
- [BigInt: arbitrary-precision integers in JavaScript · V8](https://v8.dev/features/bigint#polyfilling-transpiling)
- [2018-11-13のJS: Prettier 1.15のHTMLサポート、TypeScriptガイド、BigIntsのJavaScript実装 - JSer.info](https://jser.info/2018/11/13/prettier-1.15htmltypescriptbigintsjavascript/)
- [【C言語/C++】データ型のサイズ・範囲の一覧【32bit/64bit環境】 | MaryCore](https://marycore.jp/prog/c-lang/data-type-ranges-and-bit-byte-sizes/)

最後まで読んでいただいてありがとうございました。m(_ _)m