8

次のコードでは、関数 foo1、foo2、および foo3 は同等であることを意図しています。しかし、run foo3 がループから終了しない場合、これが当てはまる理由はありますか?

template <typename T>
T foo1()
{
   T x = T(1);
   T y = T(0);
   for (;;)
   {
      if (x == y) break;
      y = x;
      ++x;
   }
   return x;
}

template <typename T>
T foo2()
{
   T x = T(0);
   for (;;)
   {
      T y = x + T(1);
      if (!(x != y)) break;
      ++x;
   }
   return x;
}

template <typename T>
T foo3()
{
   T x = T(0);
   while (x != (x + T(1))) ++x;
   return x;
}

int main()
{
   printf("1 float:  %20.5f\n", foo1<float>());
   printf("2 float:  %20.5f\n", foo2<float>());
   printf("3 float:  %20.5f\n", foo3<float>());
   return 0;
}

注: これは、VS2010 を使用して、リリース モードで /fp precision を指定してコンパイルされました。GCC などがこのコードをどのように扱うかはわかりませんが、どんな情報でも素晴らしいでしょう。これは、foo3 で x と x+1 の値が NaN になるという問題でしょうか?

4

1 に答える 1

13

何が起こるかは、おそらく次のとおりです。x86 arch では、中間計算は 80 ビットの精度で実行できます (long double は対応する C/C++ 型です)。コンパイラは、(+1) 演算と (!=) 演算に 80 ビットすべてを使用しますが、格納する前に結果を切り捨てます。

したがって、コンパイラが実際に行うことは次のとおりです。

while ((long double)(x) != ((long double)(x) + (long double)(1))) {
  x = (float)((long double)(x) + (long double)(1));
} 

これはIEEEに完全に準拠しておらず、誰にとっても頭痛の種になりますが、これはMSVCのデフォルトです. /fp:strictこの動作を無効にするには、コンパイラ フラグを使用します。

10年ほど前の私の記憶ですので、間違っていたらご容赦ください。Microsoft の公式ドキュメントについては、こちらを参照してください

編集g++ がデフォルトでまったく同じ動作を示すことを知って非常に驚きました (i386 Linux では、たとえば -mfpmath=sse ではそうではありません)。

于 2012-05-23T06:38:16.920 に答える