小清水さんとコンピューター数学

コンピューター・数学 に関することを書きます (特に丸め誤差の話が多いです。)

コンピュータでおかしなことになる計算例 (3) ―たった1度の型変換―

小清水 です。(@curekoshimizu)

今回は C言語のsin関数 で遊んでみましょう。

最後のオチまで気づかない人がいるかも!?

というわけで、

あーなるほどねと思っても読み進めて欲しいです!

今回紹介する例

  • sinf は 単精度用 (float) の sin関数
  • sin は倍精度用 (double) の sin関数
#include <stdio.h>
#include <math.h>

int main(int argc, char** argv)
{
    double x = 1.0e30;

    printf("%.10lf\n", sinf(x));
    printf("%.10lf\n", sin(x));

    return 0;
}

出力結果

-0.7911634445
0.0093314689

そう。全然結果が違う のです!

これは sin と sinf の違いによるものなのでしょうか?

すなわち、 倍精度のsin関数単精度のsin関数

実装の問題 なのでしょうか。

それは違います。

sinf は 単精度(float) への入力のみ受け付けることから、

暗黙の型変換 による 丸め誤差が1度 発生します。

検証プログラム

倍精度用 (double) 側にも 単精度(float) への型変換を加えて確認してみましょう。

#include <stdio.h>
#include <math.h>

int main(int argc, char** argv)
{
    double x = 1.0e30;

    printf("%.10lf\n", sinf(x));
    printf("%.10lf\n", sin((float)x)); // <--- sin(x) から sin((float)x)) にした

    return 0;
}

出力結果

-0.7911634445
-0.7911634385

ほぼほぼ近いです。先程出力された 0.0093314689 とは全然違います。

このことから sin と sinf に大きな違いがあったわけではありません。

違いがあったのは、

倍精度から単精度へのたった一度の丸め誤差が入ったかどうか です。

    printf("%.10lf\n", sinf(x)); // <--- x の float への暗黙の型変換

よくよく考えると当たり前だが少し考えさせられる

実はこのプログラム

 1.0 \times 10^{30} とういとてつもなく大きな値からスタートしています。

最終的に単精度(2進24桁)へ丸めが入りますので、

math-koshimizu.hatenablog.jp

ここで説明したルールの RN(x) が入ります。

これにより丸められた値は

 1.0000000150474662\times 10^{30} です。

この値で近似されるので

その誤差なんと

 1.50474662\times 10^{22} です。

そのため、ここまで大きな誤差が生じる例となっていた ということになります。

なので 当たり前といえば当たり前 の例かもしれません。

しかしながら、この短いプログラムでそんなことに気がつきましたでしょうか。

桁数が大きい場合の型変換は思っているより近くない

ため、プログラムによっては大ダメージになる ということに注意が必要です。

プログラミングをしているときに大きな桁になっているかも…ということに

気づくことはそうそうできないため、

少し考えさせられる例かもしれません。

最後のオチ

 1.0\times 10^{30} の sin計算を 単精度で計算したのが間違い!

 1.0\times 10^{30} の sin計算 は倍精度側で出力された値の

0.0093314689...

と捉えた人は間違いですよ。

 1.0\times 10^{30} の sin計算 は倍精度側で出力された値の

0.0093314689... ではありません!

倍精度大好きっ子はよくこの結論に達しがちなのですが、

    double x = 1.0e30; // <--- 実数から浮動小数点数への丸め

ここも暗黙の型変換があります!

さっきと同じ話で こんな大きな数字の倍精度誤差も、単精度同様 尋常 じゃありません。

 \mathrm{RN}_{倍精度}(1.0\times 10^{30}) = 1000000000000000019884624838656

の sin計算 が 0.0093314689... というのはあってます!

これがタイトルの「たった一度の型変換」の意味になります。

ちなみに、 1.0\times 10^{30} の sin計算結果は 負の数になりますので、全然違いますね。

勘違いしてしまった人は

math-koshimizu.hatenablog.jp

ここを読んで是非丸め誤差の勉強を一緒にしましょう!


そんな、当たり前のようで少し勘違いしそうな例を思いついた、 昼のひとときでした。