浮動小数点数は基数2(2進法)の分数としてコンピューター・ハードウェアの中で表わされる 。例えば小数
0.125
は 1/10 + 2/100 + 5/1000 として値を持ち,同様に 2 進法の分数
0.001
は 0/2 + 0/4 + 1/8 として値を持つ。 これらの2つの分数は同一の値を持っていおり,ただ一つの実際の違いは,1 番 目は基数 10 で記述され,2 番目は基数 2 で記述されている事だ。
不運にも,ほとんどの小数は正確に 2 進法の分数として表わすことができない。 結果は,一般に,マシンに現実に格納された 2 進法の浮動小数点数が, 入力した 10 進の浮動小数点数に近似されるだけだ。
初めはその問題を基数 10 で理解すると簡単だ。分数 1/3 で考えてみる。 分数 1/3 は,基数 10 の分数として以下のように近似することができる。
0.3
さらに正確にするならば
0.33
さらに正確にするならば
0.333
さらに正確にできる。 より多くの数字を書くほど,結果はちょうど 1/3 ではないが,1/3 までますます より近似になる。
同様に,10進数 0.1 は基数2の分数では正確に表現する事はできない。 基数2では,1/10が無限に繰り返す分数だ。
0.0001100110011001100110011001100110011001100110011...
どんな有限数でも止まるか,近似が得られる。このため,以下のような事象が発生する。
>>> 0.1 0.10000000000000001
今日のほとんどのマシンにおいては,Python プロンプトで 0.1 を入力 すると上記の値が表示される。 これは,浮動小数点数の値を格納するためにハードウェアによって使用される ビットの数が,マシンで処理され変わり,Python は単に,コンピュー タによって格納された2進法の近似の真実の 10 進数の近似値を印字するだけだ。 ほとんどのマシンでは,Python が 0.1 から格納した 2 進法の近似の真 実の 10 進数の値を印字する場合,かわりに以下のように印字される!
>>> 0.1 0.1000000000000000055511151231257827021181583404541015625
Python プロンプトは(暗黙に),表示するすべての文字列表現に
repr() 関数を使用する。
浮動小数点数を repr(float)
で有効数字 17 桁の真実の 10 進数の
値に丸めるので,以下のようになる。
0.10000000000000001
repr(float)
は有効数字 17桁 の値を作成する。なぜならこの値
が(ほとんどのマシン上で)十分だからだ。
よって,すべての有限の浮動小数点数 x では 正確に
eval(repr(x)) == x
となる。
しかし,有効数字 16 桁に丸めることは正確な値としてあつかうには十分ではな
い。B.1
これは,2 進法の浮動小数点の性質だ。Python のバグでも,ソースコードのバ グでもない。浮動小数点演算を扱えるハードウェア上の,すべての言語で同じ 現象が発生する (いくつかの言語では標準もしくは全ての出力モードで異なっ た 表示 をしないかもしれない)。B.2
Python 組み込みの str() 関数は有効数字 12 桁しか生成せず,こ
れを代わりに使用したいと思うかもしれない。
この関数は x を複製する eval(str(x))
では異常である
が,出力は目で見るに好ましいかもしれない。
>>> print str(0.1) 0.1
これが実際の感覚,錯覚にあることを悟ることは重要だ: マシン中の値はちょうど 1/10 ではない。単に真実の機械の値を 表示 する際丸めている。
驚く事は他にもある。たとえば,以下を見た後
>>> 0.1 0.10000000000000001
round() 関数を使用して期待する1桁に切りすてたいという,誘惑 にかられたとする。しかし,結果は異ならない。
>>> round(0.1, 1) 0.10000000000000001
問題点は,"0.1" より格納された 2 進法の浮動小数点数の値が既に 1/10 まで最 良の可能な 2 進法の近似になっていたという事だ。したがって,再度丸めをに その値に試みるてもそれ以上丸める事はできない。 Python プロンプト上での "0.1" の印字結果は round() 関数が得 るのと同じくらい既に丸められている。
またこれは,0.1がちょうど 1/10 ではないので,0.1を 10 回加えることでちょう ど 1.0 を算出しないかもしれないということだ。
>>> sum = 0.0 >>> for i in range(10): ... sum += 0.1 ... >>> sum 0.99999999999999989
2 進法の浮動小数点演算は多くのこのような意外な事を含んでいる。 "0.1" に関する問題は,「表現エラー」の章の中で,詳細に説明する。 2進法の浮動小数点演算にともなう他の共通の意外な事象に関しては The Perils of Floating Point を参照。
これを最後に言っておく,``容易な答えはない''。それでも,浮動小数点数の ことを過度に警戒しないように! Python 浮動小数点演算における誤差は浮動小数点式のハードウェアから継承 され,ほとんどのマシン上で 1 演算当たり 2**53 に高々 1 だ。 それはほとんどの問題に妥当であるなどというものではない。しかし,それが 10 進の算術でなく,すべての浮動小数点演算で丸め誤差が存在することを心に 留めておく必要がある。B.3
問題が大きな場合が存在する一方,10 進数へ丸めた最終結果を得たいような
多くの通常の利用における浮動小数点演算では期待する結果を得られる。
str() で普通事足りるし,より洗練された制御のために Python
の %
フォーマット演算子がある。
%g
, %f
そして %e
のフォーマットコードは柔軟
性を与え,表示のための浮動小数点丸め結果を得る容易な方法だ。
この章では ``0.1'' 例について詳細に説明し,このような場合の正確な分析 をどのように実行することができるか示す。 文章は 2 進法の浮動小数点数の表現についての基礎的な知識が仮定されている。
表現エラー の原因はいくつか(現実にほとんど)の小数を,正確に 2 進 法(基数 2 )の分数として表わすことができないせいだ。 これは Python (あるいは Perl,C,C++,Java,Fortranおよび他に多くの プログラム言語)が,期待する正確な10進数をしばしば表示しない,主要な理 由だ。
>>> 0.1 0.10000000000000001
なぜ,こうなるのか? 1/10は,2 進法の分数で正確に表現可能ではない。 今日(2000年11月)のマシンはほとんどすべてIEEE-754浮動小数点演算を使用する。 また,今日のプラットフォームはほとんどすべて,IEEE-754 "倍精度" に Python 浮動小数点数を写像する。 IEEE-754の倍精度は精度 53 ビットである。したがって,入力においては,コ ンピューターは 形式 J/2**N を利用して 0.1 を最も近似した分 数に変換するよう努力する,ここでは J はちょうど 53 ビット含んで いる整数だ。これは以下のように書き直せる。
1 / 10 ~= J / (2**N)
すなわち
J ~= 2**N / 10
J がちょうど 53 ビット(>= 2**52
だが < 2**53
)持っ
ている事をから,N の最適値は 56 であるので
>>> 2L**52 4503599627370496L >>> 2L**53 9007199254740992L >>> 2L**56/10 7205759403792793L
すなわち,56 はちょうど 53 ビットに J が達っする N に見合 うただ一つの値だ。J に見合う最良の可能な値はそのとき完全なその商 だ。
>>> q, r = divmod(2L**56, 10) >>> r 6L
残りが 10 の半分以上なので,最良の近似は丸めることにより得られる。
>>> q+1 7205759403792794L
したがって,IEEE-754の倍精度の 1/10 まで最良の可能な近似は 2**56 以上 の値,もしくは
7205759403792794 / 72057594037927936
丸めたので,これは 1/10 よりもう少し大きい。 丸めなければ,商は 1/10 未満だっただろう。しかし決してこれは 正確 に 1/10 ではない!
したがって,コンピューターは 1/10 を ``見る'' 事はない。 コンピューターが見るのは上記のような正確な分数(得ることができる最良の IEEE-754倍精度の近似)だ。
>>> .1 * 2L**56 7205759403792794.0
その分数に 10**30 を掛ければ, 有効数字 30 の十進数の(切り詰められた) 値を見ることができる。
>>> 7205759403792794L * 10L**30 / 2L**56 100000000000000005551115123125L
コンピューターが格納した正確な数は,10 進数値 0.100000000000000005551115123125 とほぼ等しい事を意味する。 有効数字 17 桁に丸めることで,Python が表示する0.10000000000000001を与 える。 (C ライブラリの中で入力および出力転換を行い,すべてのIEEE-754適合プラッ トフォームに表示するだろう -- 君のコンピューターではしないかもしれな い!)
翻訳は http://urr-gw.casl.cs.uec.ac.jp/ohkubo-k/papers/index.html で公開されている。