プログラマーはIEEE745の夢をみるか
浮動小数点をあつかうコードには悩ましいものがある。文字であれ数字であれコンピュータの中では2進法で処理される。つまり0と1でしかないわけだ。たとえば、3という数値は0011になる。整数であれば丸め誤差に気をつければ問題ないが、浮動小数点になるとそうはいかなくなる。きちんと処理できず近似値になってしまう。
精度を要求するならなおさらである。だが、こればかりはどうしようもないのである。多くのプログラミング言語が浮動小数点における標準であるIEEE754フォーマットに準拠しているためだ。したがって、どのプログラミング言語を使っても、浮動小数点の計算においては同じ結果となる。
例外もあり、十進法を採用しているプログラミング言語もある。十進法BASICというプログラミング言語だ。標準で10進JIS(15桁)モードを持ち、計算はすべて10進法で計算されるため、浮動小数点の計算において、最終桁数の丸め誤差以外は気にすることなく扱うことができる。面白いのは、2進法による計算もサポートしており、浮動小数点の計算が他のプログラミング言語と同じ結果になるモードもある。切り替えて計算することも可能だ。
話を元に戻そう。次の式を見ていただきたい。
(0.1 + 0.7) * 10.0
結果を予想してみよう。「8.0」そうこれが正しい答えだが、残念ながらそうはならない。
7.999999999999999
これが計算結果だ。小数点をコンピュータが内部で正しく表現できないためにおこる問題である。これはバグでも何でもなく、標準化された仕様であり、仕方がないといえる。
だが、この問題はさらに悩ましくなる。
(0.1 + 0.6) * 10.0
0.7を0.6に変更してみただけである。計算結果は、さきほどと同じように、6.99999…と予想したかもしれない。
7.0
もうひとつ。
(0.1 + 0.2) * 10.0
3.0000000000000004
これはオマケ。
(0.1 + 0.5) * 10.0 6.0
正直いって混乱する。まあ、(0.1 + 0.2) * 10の計算結果が3.0000000000000004になるのはいいだろう。だが、8.0が7.999999999999999になるのは少し問題がある。8.0000000000000001ならまだいい。整数にキャストした場合、8が返ってくるからだ。だが、前者では7が返ってくるため、後の計算結果がおかしくなってしまう。
それで、ちょこっとテストしてみた。コードはFortranで記述してある。
program test implicit none real(8) :: b, d real(8) :: e(1:9) = (/0.10d0, 0.2d0, 0.3d0, 0.4d0, 0.5d0, 0.6d0, 0.7d0, 0.8d0, 0.9d0/) integer :: i b = 0.1d0 do i = 1, 9 d = (0.1d0 + b) * 10.0d0 print*, d b = b + 0.1d0 end do print * do i =1, 9 print *, (0.1d0 + e(i)) * 10.0d0 end do stop end program test
実行結果
0.1ずつ増分させた結果 2.0000000000000000 3.0000000000000004 4.0000000000000000 5.0000000000000000 6.0000000000000000 7.0000000000000000 7.9999999999999991 9.0000000000000000 9.9999999999999982 配列変数に入れた値を使用 2.0000000000000000 3.0000000000000004 4.0000000000000000 5.0000000000000000 6.0000000000000000 7.0000000000000000 7.9999999999999991 9.0000000000000000 10.000000000000000
0.1ずつ増分させると、最後の方で桁落ちがおきたため、このような結果になっている。それにしてもなにゆえに0.7のときだけがこんな結果になるのか。
実に悩ましいのである。
プログラミング言語によって、は数値の取り扱いには気をつけなければならないものもある。単精度の数値なのか倍精度の数値なのかといったことである。型宣言が必要なプログラミング言語では、単精度の変数に倍精度の数値を代入しようとすると、警告やエラーとなるものもある。Javaでは、浮動小数点の数値は、倍精度として取り扱われるため、単精度型の変数に代入できないようになっている。
計算においてもそうで、除算では注意しなければならない。整数と浮動小数点の数値を除算したときに、計算結果が正しくならないことがある。計算では同じ型同士で計算するのが常識だと考えるべきであろう。また、倍精度だからといって、17桁目まで正確かと言うとそうではない。最後の数値は丸められるためだ。
したがって計算において、表示する数値はそれよりも少ない桁数にするのがよい。関数電卓がそうだ。内部的には14桁で演算し、表示は10桁になっている。IEEE745を採用しているプログラミング言語では、数値計算において、桁数が多ければ多いほど、必ずしもよい結果を生むということにはならない、ということを覚えておく必要がある。