2

次のプログラム:

#include <stdio.h>

int main()
{
    double val = 1.0;
    int i;

    for (i = 0; i < 10; i++)
    {
        val -= 0.2;
        printf("%g %s\n", val, (val == 0.0 ? "zero" : "non-zero"));
    }

    return 0;
}

次の出力が生成されます。

0.8 non-zero
0.6 non-zero
0.4 non-zero
0.2 non-zero
5.55112e-17 non-zero
-0.2 non-zero
-0.4 non-zero
-0.6 non-zero
-0.8 non-zero
-1 non-zero

0.2 から 0.2 を引くときにエラーが発生する原因を誰か教えてもらえますか? これは丸め誤差ですか、それとも何か他のものですか? 最も重要なのは、このエラーを回避するにはどうすればよいですか?

編集: 5.55112e-17 がゼロに非常に近いことを考えると、結論は心配しないことのようです (その情報については @therefromhere に感謝します)。

4

4 に答える 4

2

0.2は倍精度浮動小数点数ではないため、最も近い倍精度数に丸められます。つまり、次のようになります。

            0.200000000000000011102230246251565404236316680908203125

それはかなり扱いにくいので、代わりに16進数で見てみましょう。

          0x0.33333333333334

ここで、この値を1.0から繰り返し減算するとどうなるかを見てみましょう。

          0x1.00000000000000
        - 0x0.33333333333334
        --------------------
          0x0.cccccccccccccc

正確な結果は倍精度で表現できないため、丸められます。これにより、次のようになります。

          0x0.ccccccccccccd

10進数では、これは正確に次のとおりです。

            0.8000000000000000444089209850062616169452667236328125

ここで、このプロセスを繰り返します。

          0x0.ccccccccccccd
        - 0x0.33333333333334
        --------------------
          0x0.9999999999999c
rounds to 0x0.999999999999a
           (0.600000000000000088817841970012523233890533447265625 in decimal)

          0x0.999999999999a
        - 0x0.33333333333334
        --------------------
          0x0.6666666666666c
rounds to 0x0.6666666666666c
           (0.400000000000000077715611723760957829654216766357421875 in decimal)

          0x0.6666666666666c
        - 0x0.33333333333334
        --------------------
          0x0.33333333333338
rounds to 0x0.33333333333338
           (0.20000000000000006661338147750939242541790008544921875 in decimal)

          0x0.33333333333338
        - 0x0.33333333333334
        --------------------
          0x0.00000000000004
rounds to 0x0.00000000000004
           (0.000000000000000055511151231257827021181583404541015625 in decimal)

したがって、浮動小数点演算に必要な累積丸めにより、観察している非常に小さな非ゼロの結果が生成されることがわかります。丸めは微妙ですが、決定論的であり、魔法ではなく、バグでもありません。時間をかけて学ぶ価値があります。

于 2011-04-22T17:29:23.213 に答える
1

少し詳しく説明すると、浮動小数点数の仮数がバイナリでエンコードされている場合(ほとんどの最新のFPUの場合のように)、1 / 2、1 / 4、1 / 8、の数の(倍数)の合計のみです。 1/16、...は仮数で正確に表すことができます。値0.2は、1/8 + 1/16 + ....で近似されますが、さらに小さい数もありますが、有限仮数では正確な値0.2に到達できません。

次のことを試すことができます。

 printf("%.20f", 0.2);

そして、(おそらく)0.2であると思うものは0.2ではなく、わずかに異なる数値であることがわかります(実際、私のコンピューターでは0.20000000000000001110と出力されます)。これで、0に到達できない理由がわかりました。

ただし、val = 12.5とし、ループで0.125を引くと、ゼロに達する可能性があります。

于 2011-04-21T09:40:56.930 に答える
1

浮動小数点演算は、すべての数値を正確に表すことはできません。したがって、観察したような丸め誤差は避けられません。

考えられる戦略の1つは、固定小数点形式(10進数または通貨のデータ型など)を使用することです。このようなタイプはまだすべての数値を表すことはできませんが、この例で期待どおりに動作します。

于 2011-04-21T09:41:29.017 に答える