プログラマーは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を採用しているプログラミング言語では、数値計算において、桁数が多ければ多いほど、必ずしもよい結果を生むということにはならない、ということを覚えておく必要がある。