IEEE-754浮点计算,相等和缩小

Gel*_*tor 8 c++ floating-point ieee-754 undefined-behavior

在以下代码中,函数foo1,foo2和foo3旨在等效.但是当运行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;
}
Run Code Online (Sandbox Code Playgroud)

注意:这是使用VS2010在发布模式下使用/ fp精确编译的.不确定GCC等如何处理这些代码,任何信息都会很棒.这可能是一个问题,在foo3中,x和x + 1值以某种方式变为NaN?

n. *_* m. 13

最有可能发生的事情如下.在x86 arch上,可以使用80位精度完成中间计算(long double是相应的C/C++类型).编译器将所有80位用于(+ 1)操作和(!=)操作,但在存储之前截断结果.

那么你的编译器真正做的是:

while ((long double)(x) != ((long double)(x) + (long double)(1))) {
  x = (float)((long double)(x) + (long double)(1));
} 
Run Code Online (Sandbox Code Playgroud)

这绝对不符合IEEE标准,给每个人带来无尽的麻烦,但这是MSVC的默认设置.使用/fp:strict编译器标志禁用此行为.

这是我对大约10年前问题的回忆所以请原谅我,如果这不完全正确的话.请参阅此文档以获取Microsoft官方文档.

编辑我非常惊讶地发现默认情况下g ++表现出完全相同的行为(在i386 linux上,但不是例如-mfpmath = sse).

  • +1.只使用`double`而不是`long double`就足以引起问题了,但是IIRC MS仍然喜欢使用x87 FPU,即使是64位版本,所以"long double"似乎更有可能. (4认同)
  • @ acraig5075:同意; 我不知道`/ fp:strict`在这里有什么帮助.对于`/ fp:precise`,Microsoft的文档甚至指定"表达式评估将遵循C99 FLT_EVAL_METHOD = 2",确认中间值计算为long double类型.对于`/ fp:strict`,文档没有说,但我没有看到任何理由假设`FLT_EVAL_METHOD = 0` - 就像那里的行为一样. (2认同)