LoginSignup
440
202

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-21

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

x = 0.1
print(x)
# => 0.1

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

環境

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

【前提】浮動小数点数

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

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

(-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進数に変換する過程で、有限桁で操作を打ち切った場合、変換前の数と誤差を生じます(丸め誤差)。

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

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

to_float_point_0_1.png

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

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

10進数に変換すると、

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

です。

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

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

0.1000000000000000055511151231257827021181583404541015625

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

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

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.10.1と表示されるのだと思われます。 (訂正:Python以外の言語につきましては、各言語の公式ドキュメントをご確認ください)

float_point_0_1.png

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

print(0.1000000000000000056)
# => 0.1

なお、調べた限りでは、

0.099999999999999998612221219218554324470460414886474609375
から
0.100000000000000012490009027033011079765856266021728515625

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

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

print(0.12345678901234567890)
#=> 0.12345678901234568

おまけ

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

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

続編を書きました

さて、

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

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

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

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


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

440
202
31

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
440
202