39
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptAdvent Calendar 2019

Day 5

JavaScriptのBigIntを勉強してみた

Last updated at Posted at 2019-12-04

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

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

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

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

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

参考:Number.MAX_SAFE_INTEGER - JavaScript | MDN

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とは

仕様

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

StringNumbernullundefindSymbolなどと同じくBigIntもデータ型の一つ


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

構文

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

// 末尾に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

typeof'bigint'を返す

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以上の値で試してみる

// 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を使うことで桁が大きい値でも正しく扱うことができました。

四則演算

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と同じように扱える

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

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

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

BigInt=====で試してみた

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

NumberBigIntで四則演算

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

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

NumberBigIntで比較演算

NumberBigIntは通常どおりに比較できます

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

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

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

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

sort

NumberBigIntが混在していてもソートできる

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

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

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

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

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

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

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は使えません

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)

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

// 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)

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

// 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 でうまく使えない

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

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

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

ブラウザサポート

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

※2019/11/17確認

Screen Shot 2019-11-17 at 19.45.27.png

ブラウザ対応

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

yarn add jsbi

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

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'
$ node --experimental-modules index.mjs 
(node:9580) ExperimentalWarning: The ESM module loader is experimental.
9007199254740991
9007199254740993

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

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

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

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

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'
$ 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.

参考

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

39
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?