26

x86 VM (32 ビット) で次のプログラムを発見しました。

#include <stdio.h>
void foo (long double x) {
    int y = x;
    printf("(int)%Lf = %d\n", x, y);
}
int main () {
    foo(.9999999999999999999728949456878623891498136799780L);
    foo(.999999999999999999972894945687862389149813679978L);
    return 0;
}

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

(int)1.000000 = 1
(int)1.000000 = 0

Ideone もこの動作を生成します。

これを可能にするためにコンパイラは何をしていますか?

次のプログラムが期待どおりに生成されなかった理由を追跡していたときに、この定数を見つけました0( 19 を使用すると、期待どおりに9生成されました0)。

int main () {
    long double x = .99999999999999999999L; /* 20 9's */
    int y = x;
    printf("%d\n", y);
    return 0;
}

結果が予期されたものから予期しないものに切り替わる値を計算しようとしたときに、この質問の定数にたどり着きました。

4

4 に答える 4

31

問題はlong double、正確な値 0.99999999999999999999 を格納するには、プラットフォームの精度が不十分であることです。これは、その値を表現可能な値に変換する必要があることを意味します (この変換は、実行時ではなく、プログラムの変換中に行われます)。

この変換により、最も近い表現可能な値、または次に大きいまたは小さい表現可能な値が生成されます。選択は実装によって定義されるため、実装ではどちらを使用しているかを文書化する必要があります。あなたの実装は x87 スタイルの 80bit を使用しているようでlong double、最も近い値に丸めているため、 に 1.0 の値が格納されていxます。


(64 仮数ビット)の想定形式でlong doubleは、1.0 未満の表現可能な最大数は、16 進数で次のようになります。

0x0.ffffffffffffffff

この値と次に高い表現可能な数値 (1.0) のちょうど中間にある数値は次のとおりです。

0x0.ffffffffffffffff8

非常に長い定数 0.9999999999999999999728949456878623891498136799780 は次のようになります。

0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...

最も近いものに丸める場合は明らかに切り捨てられるべきですが、コンパイラが使用している浮動小数点表現の限界に達したか、丸めバグに達したようです。

于 2013-05-30T09:41:37.763 に答える
5

コンパイラは 2 進数を使用します。ほとんどのコンパイラは同じことを行います。

wolframalpha によると、

0.99999999999999999999

次のようになります。

0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...

これは 932 ビットであり、数値を正確に表すにはまだ十分ではありません (末尾のドットを参照)。

つまり、基礎となるプラットフォームが 2 の基数を使用して数値を格納している限り、正確に を格納することはできません0.99999999999999999999

数値は正確に格納できないため、切り上げまたは切り捨てが行われます。9 が 20 個の場合は切り上げられ、9 が 19 個の場合は切り捨てられます。

この問題を回避するには、double の代わりに、10 進数を使用して数値を内部的に格納する (つまり、1 バイトあたり 2 桁の 10 進数など) か、浮動小数点の代わりに分数 (比率) を使用するサードパーティの数学/bignum ライブラリを使用する必要があります。数字。それはあなたの問題を解決するでしょう。

于 2013-05-30T18:40:50.393 に答える