JavaScriptの数値型

この記事は[Qiita　AdventCalendar JavaScript 2021　6日目](https://qiita.com/advent-calendar/2021/javascript)　の記事です

2進数の世界で動いている汎用計算機が処理する数値は、整数か浮動小数点数の2つに分けられます。C言語の型ではintやdoubleです。それぞれの型はメモリ上でのビット長が決まっており、32bitや64bitです。

言語によっては数値の精度を必要ならいくらでも伸ばしたりできるような任意精度演算
という演算システム（実際上は利用可能なメモリ容量に制限される）による演算が可能な言語もあります。

ここではJavaScriptで数値がどのように扱われているのかを記述します。

# JavaScriptの数値は1種類

JavaScriptにの数値型は1種類しかありません。
JavaScriptでは整数・浮動小数点数の区別がなくすべての数値が __64ビットの浮動小数(double型)__ のみです。

以下のようなプログラムでは一見整数を扱ってるように見えますが、これはすべて小数としてデータを扱ってることになります。


```JavaScript
const x = 1 
const y = 2 

console.log(x + y) //3
```

double型でも52ビットをはみ出ない限り、整数は厳密に計算されます。
この出力は `3`と表示されますが、内部的には `3.0 === 3` と言うことになります。


## IEEE 754 倍精度浮動小数点数

JavaScriptの数値型は __IEEE 754 倍精度浮動小数点数__ ( __double__  )です。
これは64ビットの浮動小数点数表現の規格の一つです。doubleによる浮動小数点数の表現は広く使われており、コンピューターの小数表現のスタンダードです。

計算機の世界では有限個のデータしか扱えないので、プログラムで表す数値の精度には限界があります。
以下のよく知られた例を見てみましょう

```JavaScript
console.log(0.1 + 0.2)
```

特に意識しなければ上記の例は `0.3` と出力されそうですが、実際には `0.30000000000000004` と出力されるはずです。

こういう例もある

```JavaScript
console.log(0.3 === 0.1 + 0.2)
/* 出力：false */
```

なぜこのようなことになるかと言うと、`0.1` や `0.2`という数が切りの悪い数なので正確に表現することができないのが原因です。
 `0.1` は2進数で表すと
`0.00011001100...`
と無限小数になり、double型で表現できる一番近い値に丸められます。
丸められた結果を10進数に戻すと
`0.1000000000000000055511151231257827021181583404541015625` 
というような値になります。


`0.2`も同様に丸められ10進数に戻すと以下のような値になります。
```
0.200000000000000011102230246251565404236316680908203125
```

`0.1 + 0.2` の計算結果は以下の通りです。

 ```
0.3000000000000000444089209850062616169452667236328125
```

これは `0.1` 、`0.2` の丸められた誤差、 `0.1 + 0.2` の加算で発生した丸めの誤差が計算結果に蓄積されているということです。これは、JavaScriptが実際に計算するのは __正確な__ 合計です。

プログラムで`0.3` と書いた時点でもすでに誤差は発生しています。
コンピューターは実際の `0.3` に最も近いdouble型で表現な可能な数(↓)で解釈されます。

```
0.299999999999999988897769753748434595763683319091796875
```

これは `0.3`をdoubleで表現しようとすると負の方向に誤差が発生しますが、 `0.1` と `0.2` ともに正の誤差を持っており、これらが蓄積した `0.1 + 0.2`  は `0.3` から離れるため、`0.3` ( double型で表現可能な `0.3` に最も近い値)からずれてしまいます。このズレはビット表現ではわずか1ビット。`0.3` にちょうど１ビットです。これは `0.3` にちょうど１ビット分の値である $2^{-54}$ を足すと0.1 + 0.2になります。

```JavaScript
// これはtrueと出力される
console.log(0.3 + 2 ** (-54) === 0.1 + 0.2)
```
ここで言いたいことは、このような挙動はJavaScriptに関わらず __浮動小数点数(IEE754)の仕様__ であり、
コンピューターで小数を表現しようとする限り避けようのないものであると言うことです。

# JavaScriptの数値精度は52ビット

JavaScriptの数値は整数・浮動小数点数で区別がないことは冒頭で説明しましたが、
これは __整数だろうと小数だろうとすべてdoubleで表現している__ ということです。

つまり __JavaScriptの整数の精度も52ビット__ ということになります。
整数と小数を区別する他のプログラミング言語では整数の精度は32ビットだったり64ビットだったりするので、それに比べるとかなり中途半端に感じると思います。ただそれらの言語と比べるとJavaScriptでの整数はdoubleであることに由来する __下の桁から大雑把になっていく__ という挙動をすることがあります。


整数の精度が52ビットということは、 $0$から$2^{53}-1$ までの整数は正確に解釈できることを意味します。これで重要なのは、ここで正確に表現できるというのは __1つ違う隣の整数と区別できる__ 事を意味します。以下の例は$2^{53}-1$が両隣の数と区別できることを表しています。

```JavaScript
console.log(2 ** 53 - 2 === 2 ** 53 -1)
// faise (2 ** 53 - 2 === 2 ** 53 -1は違うもとの解釈される
console.log(2 ** 53 - 1 == 2 ** 53)
// false (2 ** 53 - 1 == 2 ** 53は違うもと解釈される
```

## NaN と Infinity、+0 と- 0

JavaScriptには `NaN` と `Infinity` という特別な数値が存在します。 `NaN`はNot a Numberの略で有ることは知られていますが、JavaScriptでは数値の一種なので型を調べると以下のように数値型となります。

```JavaScript
console.log(typeof NaN)
// number
```
`0 / 0` という計算をするとNaNとなります。
NaNの面白い挙動は `NaN === NaN` とするとfalseとなるところです。 `NaN < NaN` 等のNaN含む比較演算子は全部falseになります。ある数値をNaNか判定したい場合は `isNaN` を使うといいでしょう。

`Infinity` も同様で、これは「無限大」を表現する特別な数値で、IEEE 754由来です。無限大には正の無限大、負の無限大があります。

doubleには `+0` と `-0` という2種類の0が存在し、JavaScriptでも2種類の0が確認できます。(通常の0は正の `+0` となります）。`+0` と `-0` を === で比較してもtrueとなるのが特徴です。

このようなJavaScriptにおいて使われている等値比較演算子である `===` もIEEE 754の影響をうけています。
ES2015以降では、IEEE 754の影響を排除した等値比較の手段として `Object.is` という関数が用意されてるようです

# まとめ

ここではJavaScriptの数値型について書いてみました。

ポイントはJavaScriptの数値型は整数と浮動小数点数を区別せず、
__IEEE 754倍精度浮動小数点数__ で表す点です。

その結果整数が52ビットの精度をもっており、浮動小数点数特有の `0.3 !== 0.1 + 0.2` のような挙動が現れます。どの挙動がJavaScript特有の物でどの挙動がIEEE 754由来の話なのかをよく考えてコーディングするといいでしょう。

# 参考文献
- https://qntm.org/notpointthree