21

ほとんどの場合、浮動小数点比較テストは値の範囲 (abs(xy) < epsilon) を使用して実装する必要があることは理解していますが、自己減算は結果がゼロになることを意味しますか?

// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )

私の推測では、nan/inf は特殊なケースかもしれませんが、単純な値の場合に何が起こるかにもっと興味があります。

編集:

誰かが参照 (IEEE 浮動小数点標準) を引用できる場合は、喜んで回答しますか?

4

6 に答える 6

61

あなたがほのめかしたように、inf - infNaNであり、ゼロには等しくありません。同様に、NaN - NaNですNaN。ただし、任意の有限浮動小数点数x, x - x == 0.0(丸めモードに応じて、 の結果は負のゼロになる場合がありますが、浮動小数点演算ではx - x負のゼロと比較すると等しい) は事実です。0.0

編集:これは IEEE-754 標準で定められた規則の緊急の特性であるため、明確な標準参照を提供するのは少し難しいです。具体的には、箇条 5 で定義された操作が正しく丸められるという要件から導き出されます。減算はそのような操作であり (セクション 5.4.1「算術演算」)、正しく丸められた結果はx - x適切な符号のゼロです (セクション 6.3、パラグラフ 3):

反対の符号を持つ 2 つのオペランドの合計 (または同様の符号を持つ 2 つのオペランドの差) が正確にゼロの場合、その合計 (または差) の符号は、roundTowardNegative を除くすべての丸め方向属性で +0 になります。その属性の下では、正確なゼロサム (または差) の符号は -0 になります。

したがって、 の結果は であるx - x必要があり、したがって、 (セクション 5.11、パラグラフ 2)+/- 0と比較する必要があります。0.0

比較ではゼロの符号は無視されます。

さらに編集:バグのあるコンパイラがそのアサートを起動できなかったと言っているわけではありません。あなたの質問はあいまいです。falsexであるような有限の浮動小数点数はありません。x - x == 0ただし、それはあなたが投稿したコードがチェックするものではありません。C スタイル言語の特定の式がゼロ以外の値に評価できるかどうかをチェックします。特に、特定のプラットフォームでは、特定の (よく考えられていない) コンパイラの最適化により、xその式の変数の 2 つのインスタンスが異なる値を持つ可能性があり、アサーションが失敗する可能性があります (特に、xが定数ではなく何らかの計算の結果である場合)。 、表現可能な値)。これは、これらのプラットフォームの数値モデルのバグですが、発生しないわけではありません。

于 2010-04-21T21:22:44.743 に答える
4

表現が変換された場合 (たとえば、x86 で 64 ビットのメモリ形式から 80 ビットの内部レジスタ形式に変換された場合)、状況によってはアサーションが発生する可能性があると予想されます。

于 2010-04-21T21:31:23.023 に答える
3

はい、特別な場合x-xを除いて、常に 0 になります。ただしx*(1/x)、常に 1 になるとは限りません ;-)

于 2010-04-21T21:20:38.130 に答える
3

はい、特別な場合を除いて、自己減算は常にゼロになるはずです。

この問題は、指数と仮数が調整される比較の前に加算、減算、乗算、または除算を行った場合に発生します。指数が同じ場合は仮数を引き、同じ場合はすべてゼロになります。

http://grouper.ieee.org/groups/754/

于 2010-04-21T21:21:11.983 に答える
3

主な質問に対する私の答え: 「xx == 0 が false である x の浮動小数点値はありますか?」つまり、少なくとも Intel プロセッサでの浮動小数点の実装では、"+" および "-" 演算で算術アンダーフローが発生しないため、xx == 0 が false である x を見つけることができません。同じことが、IEEE 754-2008 をサポートするすべてのプロセッサに当てはまります(以下の参考文献を参照)。

別のあなたの質問に対する私の短い答え: if (xy == 0) は if (x == y) とまったく同じくらい安全なので、 assert(xx == 0) は問題ありませ。 xy)。

理由は以下。float/double の数値は、仮数と 2 進指数の形式でメモリに保持されます。標準ケースでは、仮数は正規化されています: >= 0.5 かつ < 1<float.h>です。IEEE 浮動小数点標準からいくつかの定数を見つけることができます。私たちにとって興味深いのは、次のことだけです

#define DBL_MIN         2.2250738585072014e-308 /* min positive value */
#define DBL_MIN_10_EXP  (-307)                  /* min decimal exponent */
#define DBL_MIN_EXP     (-1021)                 /* min binary exponent */

しかし、 DBL_MIN未満の倍数を使用できることを誰もが知っているわけではありません。DBL_MIN 未満の数値で算術演算を行う場合、この数値は正規化されないため、「丸め誤差」なしで整数 (仮数部のみの演算) と同じようにこの数値を操作できます。

備考: 個人的には丸め誤差」という言葉は使わないようにしています。これらの操作は、浮動小数点数のような同じコンピューター番号の +、-、*、および / 操作と同じではありません。浮動小数点数のサブセットには決定論的な操作があり、それぞれのビット数が明確に定義された形式 (仮数、指数) で保存できます。このような float のサブセットは、computer float numberと名付けることができます。したがって、従来の浮動小数点演算の結果は、コンピューターの浮動小数点数セットに射影されます。このような射影操作は決定論的であり、if x1 >= x2 then x1*y >= x2*y のような多くの機能を備えています。

長い発言で申し訳ありませんが、本題に戻ります。

DBL_MIN より小さい数値で操作した場合に何が得られるかを正確に示すために、C で小さなプログラムを作成しました。


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

void DumpDouble(double d)
{
    unsigned char *b = (unsigned char *)&d;
    int i;

    for (i=1; i<=sizeof(d); i++) {
        printf ("%02X", b[sizeof(d)-i]);
    }
    printf ("\n");
}

int main()
{
    double x, m, y, z;
    int exp;

    printf ("DBL_MAX=%.16e\n", DBL_MAX);
    printf ("DBL_MAX in binary form: ");
    DumpDouble(DBL_MAX);

    printf ("DBL_MIN=%.16e\n", DBL_MIN);
    printf ("DBL_MIN in binary form: ");
    DumpDouble(DBL_MIN);

    // Breaks the floating point number x into its binary significand
    // (a floating point value between 0.5(included) and 1.0(excluded))
    // and an integral exponent for 2
    x = DBL_MIN;
    m = frexp (x, &exp);
    printf ("DBL_MIN has mantissa=%.16e and exponent=%d\n", m, exp);
    printf ("mantissa of DBL_MIN in binary form: ");
    DumpDouble(m);

    // ldexp() returns the resulting floating point value from
    // multiplying x (the significand) by 2
    // raised to the power of exp (the exponent).
    x = ldexp (0.5, DBL_MIN_EXP);   // -1021
    printf ("the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(x);

    y = ldexp (0.5000000000000001, DBL_MIN_EXP);
    m = frexp (y, &exp);
    printf ("the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(y);
    printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);

    y = ldexp ((1 + DBL_EPSILON)/2, DBL_MIN_EXP);
    m = frexp (y, &exp);
    printf ("the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(y);
    printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);

    z = y - x;
    m = frexp (z, &exp);
    printf ("z=y-x in binary form: ");
    DumpDouble(z);
    printf ("z will be displayed by printf(%%.16e) as %.16e\n", z);
    printf ("z has mantissa=%.16e and exponent=%d\n", m, exp);

    if (x == y)
        printf ("\"if (x == y)\" say x == y\n");
    else
        printf ("\"if (x == y)\" say x != y\n");

    if ((x-y) == 0)
        printf ("\"if ((x-y) == 0)\" say \"(x-y) == 0\"\n");
    else
        printf ("\"if ((x-y) == 0)\" say \"(x-y) != 0\"\n");
}

このコードは、次の出力を生成しました。

DBL_MAX=1.7976931348623157e+308
DBL_MAX in binary form: 7FEFFFFFFFFFFFFF
DBL_MIN=2.2250738585072014e-308
DBL_MIN in binary form: 0010000000000000
DBL_MIN has mantissa=5.0000000000000000e-001 and exponent=-1021
mantissa of DBL_MIN in binary form: 3FE0000000000000
the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000000
the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
z=y-x in binary form: 0000000000000001
z will be displayed by printf(%.16e) as 4.9406564584124654e-324
z has mantissa=5.0000000000000000e-001 and exponent=-1073
"if (x == y)" say x != y
"if ((x-y) == 0)" say "(x-y) != 0"

したがって、DBL_MIN 未満の数値で作業すると、正規化されないことがわかります (「 」を参照0000000000000001)。これらの数値は、整数と同様に「エラー」なしで処理しています。したがって、割り当てた場合、 y=xthenif (x-y == 0)は とまったく同じように安全if (x == y)であり、正常にassert(x-x == 0)動作します。この例では、z = 0.5 * 2 ^(-1073) = 1 * 2 ^(-1072) です。この数は実際には double で保存できる最小の数です。DBL_MIN 未満の数値を使用したすべての算術演算は、整数に 2 ^(-1072) を乗算した場合と同様に機能します。

したがって、Intel プロセッサを搭載した Windows 7 コンピュータではアンダーフローの問題は発生しません。誰かが別のプロセッサを持っている場合、私たちの結果を比較するのは興味深いでしょう.

- または + 操作で算術アンダーフローを生成する方法を誰か知っていますか? 私の実験はそのように見えますが、それは不可能です。

EDITED : コードとメッセージを読みやすくするために、コードを少し変更しました。

追加リンク: 私の実験によると、私の Intel Core 2 CPU ではhttp://grouper.ieee.org/groups/754/faq.html#underflowが完全に正しいことがわかりました。この計算方法では、「+」および「-」の浮動小数点演算でアンダーフローが発生しません。私の結果は、Strict (/fp:strict) または Precise (/fp:precise) Microsoft Visual C コンパイラ スイッチに依存していません ( http://msdn.microsoft.com/en-us/library/e7s85ffb%28VS.80%29を参照)。 .aspxおよびhttp://msdn.microsoft.com/en-us/library/Aa289157 )

もう1つ(おそらく最後のリンク)リンクと私の最終的な発言: http://en.wikipedia.org/wiki/Subnormal_numbersの良いリファレンスを見つけました。ここでは、以前に書いたものと同じように説明されています。非正規数または非正規化数 ( IEEE 754-2008などでサブノーマル数と呼ばれることが多い) を含めるには、次のステートメントに従います。

「非正規数は、浮動小数点数の加算と減算が決してアンダーフローしないという保証を提供します。近くにある 2 つの浮動小数点数には、常に表現可能なゼロ以外の差があります。段階的アンダーフローがないと、値が等しくなくても、減算 a-b がアンダーフローしてゼロになる可能性があります。」</p>

したがって、すべての結果は、IEEE 754-2008 をサポートするすべてのプロセッサで正しくなければなりません。

于 2010-04-21T23:50:49.760 に答える
1

マークの発言について - このリンクhttp://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18をチェックしてください。(ただし、それがあなたの状況に当てはまるかどうかはわかりません。)

于 2010-04-21T21:39:54.230 に答える