C和C ++中的浮点运算差异

时间:2019-03-14 15:03:25

标签: c++ c

解决方案

由于@Michael Veksler的回答,我走上了正确的道路,寻求解决方案。 @Christoph在此post中建议尝试使用不同的编译器标志来设置浮点运算的精度。

对我来说,-mpc32标志解决了这个问题。


我必须将C ++代码转换为C代码,因为新目标将没有C ++编译器。 我遇到了一件奇怪的事情,在C程序中运行时,与在C ++程序中运行时,数学方程得出的结果不同。

方程式:

float result = (float)(a+b-c+d+e);

方程式的元素都是浮点数。我通过使用

检查每个元素的内存内容
printf("a : 0x%02X%02X%02X%02X\n",((unsigned char*)&a)[0],((unsigned char*)&a)[1],((unsigned char*)&a)[2],((unsigned char*)&a)[3]);

在C和C ++中, a b c d e 相等,但是结果不同。

用C计算的示例:

a : 0x1D9969BB
b : 0x6CEDC83E
c : 0xAC89452F
d : 0xD2DC92B3
e : 0x4FE9F23C
result : 0xCD48D63E

还有一个C ++示例:

a : 0x1D9969BB
b : 0x6CEDC83E
c : 0xAC89452F
d : 0xD2DC92B3
e : 0x4FE9F23C
result : 0xCC48D63E

当我将等式分成较小的部分时,例如r = a + b然后是r = r - c,依此类推,结果是相等的。 我有一台64位Windows计算机。

有人可以解释为什么会这样吗? 对于这个菜鸟问题,我感到很抱歉,我才刚开始。

编辑

我使用带有选项的最新版MinGW

  

-O0 -g3-墙-c -fmessage-length = 0

编辑2

很抱歉...

以下是与C中上述十六进制对应的值:

a : -0.003564424114301801
b : 0.392436385154724120
c : 0.000000000179659565
d : -0.000000068388217755
e : 0.029652265831828117
r : 0.418524175882339480

这是C ++:

a : -0.003564424114301801
b : 0.392436385154724120
c : 0.000000000179659565
d : -0.000000068388217755
e : 0.029652265831828117
r : 0.418524146080017090

它们的打印方式像printf("a : %.18f\n",a);

在编译时不知道这些值,在整个执行过程中,该方程位于多次调用的函数中。方程的元素在函数内部计算。

我还观察到了一件奇怪的事情:我在一个新的“纯”项目(针对C和C ++)中运行了精确的方程式,即仅运行主体本身。元素的值与上面的元素相同(在float中)。两者的结果均为r : 0xD148D63E。与@geza的评论相同。

2 个答案:

答案 0 :(得分:15)

简介:鉴于问题不够详尽,我只能推测一下臭名昭著的gcc's 323 bug。正如错误ID低的提示,该错误一直存在。该错误报告自2000年6月以来一直存在,目前有94个(!)重复项,而最后一个报告仅在半年前(2018-08-28)报告。该错误仅影响Intel计算机(如cygwin)上的32位可执行文件。我假设OP的代码使用x87 floating point instructions,这是32位可执行文件的默认设置,而SSE指令只是可选的。由于64位可执行文件比32位更为普遍,并且不再依赖x87指令,因此该错误被修复的可能性为零。

错误描述: x87体系结构具有80位浮点寄存器。 float仅需要32位。错误是x87浮点运算始终以80位精度完成(取决于硬件配置标志)。这种额外的精度使精度非常不稳定,因为它取决于何时将寄存器溢出(写入)到内存中。

如果将80位寄存器溢出到内存中的32位变量中,则会失去额外的精度。如果这是在每个浮点运算之后发生的,则这是正确的行为(因为float应该是32位)。但是,溢出到内存会使速度变慢,并且没有编译器编写器希望可执行文件运行缓慢。因此,默认情况下,这些值不会溢出到内存中。

现在,有时值会溢出到内存中,有时则不会。它取决于优化级别,编译器试探法以及其他看似随机的因素。即使使用-O0,处理x87寄存器到内存的策略也可能略有不同,从而导致结果略有不同。溢出的策略可能是您遇到的C和C ++编译器之间的差异。

解决方法: 有关处理此问题的方法,请阅读c handling of excess precision。尝试使用-fexcess-precision=standard运行编译器,并将其与-fexcess-precision=fast进行比较。您也可以尝试玩-mfpmath=sse

注意:根据C ++标准,这实际上不是错误。但是,根据GCC的文档,这是一个错误,该文档声称在Intel体系结构(就像在许多其他体系结构上一样)遵循IEEE-754 FP standard。显然,错误323违反了IEE-754标准。

注意2 :在某些优化级别上,会调用-fast-math,并且所有关于高精确度和评估顺序的赌注都被取消。

编辑我已经在Intel 64位系统上模拟了所描述的行为,并获得了与OP相同的结果。这是代码:

int main()
{
    float a = hex2float(0x1D9969BB);
    float b = hex2float(0x6CEDC83E);
    float c = hex2float(0xAC89452F);
    float d = hex2float(0xD2DC92B3);
    float e = hex2float(0x4FE9F23C);
    float result = (float)((double)a+b-c+d+e);
    print("result", result);
    result = flush(flush(flush(flush(a+b)-c)+d)+e);
    print("result2", result);
} 

支持功能的实现:

float hex2float(uint32_t num)
{
    uint32_t rev = (num >> 24) | ((num >> 8) & 0xff00) | ((num << 8) & 0xff0000) | (num << 24);
    float f;
    memcpy(&f, &rev, 4);
    return f;
}
void print(const char* label, float val)
{
    printf("%10s (%13.10f) : 0x%02X%02X%02X%02X\n", label, val, ((unsigned char*)&val)[0],((unsigned char*)&val)[1],((unsigned char*)&val)[2],((unsigned char*)&val)[3]);
}
float flush(float x)
{
    volatile float buf = x;
    return buf;
}

运行此命令后,结果之间的差异完全相同:

  result ( 0.4185241461) : 0xCC48D63E
 result2 ( 0.4185241759) : 0xCD48D63E

由于某种原因,这与问题中描述的“纯”版本有所不同。有一次我也得到了与“纯”版本相同的结果,但是从那时起,这个问题就改变了。原始问题中的原始值是不同的。他们是:

float a = hex2float(0x1D9969BB);
float b = hex2float(0x6CEDC83E);
float c = hex2float(0xD2DC92B3);
float d = hex2float(0xA61FD930);
float e = hex2float(0x4FE9F23C);

并使用这些值得到的输出为:

   result ( 0.4185242951) : 0xD148D63E
  result2 ( 0.4185242951) : 0xD148D63E

答案 1 :(得分:5)

C和C ++标准都允许比标称类型更精确地评​​估浮点表达式。因此,即使类型为a+b-c+d+e,也可以使用double来评估float,并且编译器可以用其他方式优化表达式。特别是,使用精确数学实质上是使用无限的精度,因此编译器可以根据数学属性而不是浮点算术属性自由地优化或重新排列表达式。

出于某种原因,看来您的编译器正在选择使用这种自由来在不同情况下对表达式进行不同的评估(这可能与所编译的语言有关,或者可能是由于C和C ++代码之间的其他差异)。一个可能正在评估(((a+b)-c)+d)+e,而另一个可能正在评估(((a+b)+d)+e)-c或其他变化形式。

在两种语言中,都要求编译器在执行强制转换或分配时“放弃”多余的精度。因此,您可以通过插入强制转换或作业来强制执行某个评估。强制转换会使表达式混乱,因此赋值可能更容易阅读:

float t0 = a+b;
float t1 = t0-c;
float t2 = t1+d;
float result = t2+e;