0.1は浮動小数点数で正確に表せないのに、printしたときに0.1と表示されるのはなぜか

``x``に``0.1``を代入し、コンソールに表示すると``0.1``と表示されます。

```python
x = 0.1
print(x)
# => 0.1
```

当たり前のことに感じますが、``0.1``は浮動小数点数(IEEE 754)では正確に表現できません。
なのに``print``をしたときに``0.1``と表示されるのは不思議です。
このことについて分かったことを書いておきます。

# 環境
この記事ではPython 3.7を使用しています。

# 【前提】浮動小数点数
この記事で、以降"浮動小数点数"という場合は、"IEEE 754 倍精度"のことを指します。

浮動小数点数のフォーマットは、数を以下の形式に変換し、``sign``、``exp``、``frac``を順に並べたものです。

```math
(-1)^{sign} \times 2^{exp - 1023} \times (1 + frac \times 2^{-52})
```

それぞれの記号の名前と範囲は以下の通りになります。

|記号|日本語名|英語名|範囲|
|---|---|---|---|
|sign|符号|sign|0か1|
|exp|指数|exponent|1から2,046|
|frac|仮数|fraction|0から4,503,599,627,370,495|

10進数をこの形式に変換する方法は、こちらを参照してください。
http://www.picfun.com/mathlib02.html

10進数の小数を2進数の小数にする方法はこちらがわかりやすいです。
https://mathwords.net/syosuu2sin

多くの場合、10進数の小数は2進数では循環小数となります。[^1]
10進数の小数を2進数に変換する過程で、有限桁で操作を打ち切った場合、変換前の数と誤差を生じます(丸め誤差)。

[^1]: 例えば、0より大きく1より小さい数で、小数点以下3桁以内で表される数(999個ある)のうち、2進数に変換した時に循環小数にならないのはわずか7個です。

# 例えば0.1を浮動小数点数で表現したとき

0.1を浮動小数点数に変換します。
変換には、以下のツールを利用しました。
https://tools.m-bsys.com/calculators/ieee754.php

<img width="896" alt="to_float_point_0_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/82699691-ef8a-b613-f7ea-243bf1ed0e19.png">


``0.1``は、浮動小数点数の2進数表現では、

```
符号 = 0
指数 = 01111111011
仮数 = 1001100110011001100110011001100110011001100110011010
```

10進数に変換すると、

```
符号 = 0
指数 = 1019
仮数 = 2702159776422298
```

です。

上記の浮動小数点数の式に当てはめると、

```math
\begin{align}
&(-1)^{0} \times 2^{1019-1023} \times (1 + 2702159776422298 \times 2^{-52})\\
&= 1 \times 2^{-4} \times (2^{52} \times 2^{-52} + 2702159776422298 \times 2^{-52})\\
&=  2^{-4} \times (4503599627370496 \times 2^{-52} + 2702159776422298 \times 2^{-52})\\
&=  2^{-4} \times 7205759403792794 \times 2^{-52}\\
&=  7205759403792794 \times 2^{-56}\\
&=  \frac{7205759403792794}{72057594037927936}\\
&= 0.1000000000000000055511151231257827021181583404541015625
\end{align}
```

となり、コンピュータ上で0.1は

```math
0.1000000000000000055511151231257827021181583404541015625
```

として扱われていることがわかります。

これが、例えば``0.1``を3回足したときに``0.3``ぴったりにならない原因です(正確にいうと、"ならない"というより"表示されない")。

```python
print(0.1 + 0.1 + 0.1)
# => 0.30000000000000004
```

# printした時の挙動
本題の、``0.1``が浮動小数点数で正確に表せないのに、``print``したときに``0.1``と表示されるのはなぜか、ですが、Pythonの公式ページに答えが書いてあります。

> 昔の Python は、プロンプトと repr() ビルトイン関数は 17 桁の有効数字を持つ 0.10000000000000001 のような10進数の値を選んで表示していました。 Python 3.1 からは、ほとんどの場面で 0.1 のような最も短い桁数の10進数の値を選ぶようになりました。
https://docs.python.org/ja/3/tutorial/floatingpoint.html

とのことです。

つまり、浮動小数点数で、``0.1``と同じ表現になる数のうち、一番短い表現が選択されるということです。~~Python以外の言語でも同じような理由で``0.1``が``0.1``と表示されるのだと思われます。~~ (訂正:Python以外の言語につきましては、各言語の公式ドキュメントをご確認ください)

![float_point_0_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/ba55413d-8003-d6ed-f502-6ce36f6c195f.png)


逆に言えば、例えば``0.1``に非常に近い数であれば、``0.1``と表示されます。

```python
print(0.1000000000000000056)
# => 0.1
```

なお、調べた限りでは、

```python
0.099999999999999998612221219218554324470460414886474609375
から
0.100000000000000012490009027033011079765856266021728515625
```
までが、``print``したときに``0.1``と表示される範囲です。この範囲にある数は、``0.1``と同じ浮動小数点数に変換されるためです。


1番短い表現が特にない場合は17桁の有効数字を持つ数値を表示するようです。

```python
print(0.12345678901234567890)
#=> 0.12345678901234568
```

# おまけ

Pythonで小数点以下の桁数を指定するには以下のようにします。入力した数値と出力に誤差があることが確認できます。

```python
print(f'{0.1:.20f}')
# => 0.10000000000000000555
```

# 続編を書きました
さて、

> これが、例えば``0.1``を3回足したときに``0.3``ぴったりにならない原因です(正確にいうと、"ならない"というより"表示されない")。
> 
```python
print(0.1 + 0.1 + 0.1)
# => 0.30000000000000004
```

と書きました。
`0.1`の浮動小数点数を10進数で表したものでは18桁目に初めて`5`が出るのですが、`0.1`を3回足すと17桁目に`4`が現れます。
これでは計算が合いません。

以下の記事でこの現象を説明しました。

- [0.1の浮動小数点数は0.1より大きいのに、10回足すと1.0より小さいのはなぜか【前編】](https://qiita.com/yucatio/items/85a8e8d381d300092295)
- [0.1の浮動小数点数は0.1より大きいのに、10回足すと1.0より小さいのはなぜか【後編】](https://qiita.com/yucatio/items/b5ed14bbd1e9a751b703#_reference-57cd7c907b8fa0bf0291)