原始双重类型比较的GCC问题

时间:2010-03-23 05:47:26

标签: c++ gcc double compare

我有以下一些代码,但是当使用GCC 4.4使用各种优化标志进行编译时,我在运行时会得到一些意想不到的结果。

#include <iostream>

int main()
{
   const unsigned int cnt = 10;
   double lst[cnt] = { 0.0 };
   const double v[4] = { 131.313, 737.373, 979.797, 731.137 };

   for(unsigned int i = 0; i < cnt; ++i) {
      lst[i] = v[i % 4] * i;
   }

   for(unsigned int i = 0; i < cnt; ++i) {
      double d = v[i % 4] * i;
      if(lst[i] != d) {
         std::cout << "error @ : " << i << std::endl;
         return 1;
      }
   }
   return 0;
}
  • 编译时使用: “g ++ -pedantic -Wall -Werror -O1 -o test test.cpp” 我得到以下输出:“error @ :3“

  • 编译时使用: “g ++ -pedantic -Wall -Werror -O2 -o test test.cpp” 我得到以下输出:“error @ :3“

  • 编译时: “g ++ -pedantic -Wall -Werror -O3 -o test test.cpp” 我没有错误

    < / LI>
  • 编译时: “g ++ -pedantic -Wall -Werror -o test test.cpp” 我没有错误

我不认为这是与舍入有关的问题,或者比较中的epsilon差异。我已经尝试过使用英特尔v10和MSVC 9.0,它们似乎都按预期工作。我相信这应该只是一个按位比较。

如果我将if语句替换为以下内容: if (static_cast<long long int>(lst[i]) != static_cast<long long int>(d)) ,并添加“-Wno-long-long”,我就不会出现任何错误运行时的优化模式。

如果我在“返回1”之前添加 std::cout << d << std::endl; ,我在运行时的任何优化模式都不会出错。

这是我的代码中的错误,还是GCC有问题以及它处理double类型的方式?

注意:我刚刚尝试使用gcc版本4.3和3.3,但没有显示错误。

解决方案:Mike Dinsdale注意到以下错误报告:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 GCC团队似乎并非完全确定关于问题的性质。

正如错误报告中所建议的,可能的解决方案是使用ffloat-store选项。我已经尝试了这个并且它有效,但是从性能的角度来看,结果并不是很好,尽管ymmv。

3 个答案:

答案 0 :(得分:7)

结果取决于优化设置这一事实表明它可能是x87扩展精度混乱的东西(如Michael Burr所说)。

这是我使用的一些代码(在x86处理器上使用gcc)来关闭扩展精度:

static const unsigned int PRECISION_BIT_MASK = 0x300;
///< bitmask to mask out all non-precision bits in the fpu control word \cite{INTEL}.
static const unsigned int EXTENDED_PRECISION_BITS = 0x300;
///< add to the fpu control word (after zeroing precision bits) to turn on extended precision \cite{INTEL}.
static const unsigned int STANDARD_PRECISION_BITS = 0x200;
///< add to the fpu control word (after zeroing precision bits) to turn off extended precision \cite{INTEL}.

void set_fpu_control_word(unsigned int mode)
{
  asm ("fldcw %0" : : "m" (*&mode));
}

unsigned int get_fpu_control_word()
{
  volatile unsigned int mode = 0;
  asm ("fstcw %0" : "=m" (*&mode));
  return mode;
}

bool fpu_set_extended_precision_is_on(bool state)
{
  unsigned int old_cw = get_fpu_control_word();
  unsigned int masked = old_cw & ~PRECISION_BIT_MASK;
  unsigned int new_cw;
  if(state)
    new_cw = masked + EXTENDED_PRECISION_BITS;
  else
    new_cw = masked + STANDARD_PRECISION_BITS;
  set_fpu_control_word(new_cw);
  return true;
}

bool fpu_get_extended_precision_is_on()
{
  unsigned int old_cw = get_fpu_control_word();
  return  ((old_cw & PRECISION_BIT_MASK) == 0x300);
}

或者您可以使用valgrind运行您的代码,它不会模拟80位寄存器,对于像这样的短程序来说可能更容易!

答案 1 :(得分:4)

问题可能是在存储表达式的结果时丢失一些精度的结果,而编译器在本地作为优化时没有这样做:

double d = v[i % 4] * i;  // the result, `d`, might be kept in a register 
                          //   instead of storing it in a memory location, 
                          //   keeping full precision

if(lst[i] != d) {         // the value stored in lst[i] may have lost some 
                          //   precision since it had to be stored in memory,
                          //   which might not be able to hold the full 
                          //   precision that the expression generated

C99标准在6.3.1.8/2“常用算术转换”中说:

  

浮动操作数的值和浮动表达式的结果可以是   代表的精度和范围比该类型所要求的更高;类型不是   由此改变了。

答案 2 :(得分:3)

x86中浮点寄存器的宽度与RAM中double的宽度不同。因此,比较可能成功或失败,完全取决于编译器如何决定优化浮点值的负载。