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

Edu*_*ate 8 c c++

感谢@Michael Veksler的回答,我处于正确的位置以寻找解决方案。@Christoph,在这个岗位,建议尝试不同的编译器标志设置浮点运算的精度。

对我来说,-mpc32旗帜解决了这个问题。


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

方程:

float result = (float)(a+b-c+d+e);
Run Code Online (Sandbox Code Playgroud)

等式的元素都是浮点数。我通过检查每个元素的内存内容

printf("a : 0x%02X%02X%02X%02X\n",((unsigned char*)&a)[0],((unsigned char*)&a)[1],((unsigned char*)&a)[2],((unsigned char*)&a)[3]);
Run Code Online (Sandbox Code Playgroud)

在C和C ++中,abcde相等,但结果不同。

C语言中的计算示例:

a : 0x1D9969BB
b : 0x6CEDC83E
c : 0xAC89452F
d : 0xD2DC92B3
e : 0x4FE9F23C
result : 0xCD48D63E
Run Code Online (Sandbox Code Playgroud)

还有一个C ++示例:

a : 0x1D9969BB
b : 0x6CEDC83E
c : 0xAC89452F
d : 0xD2DC92B3
e : 0x4FE9F23C
result : 0xCC48D63E
Run Code Online (Sandbox Code Playgroud)

当我在更小的部分的方程分离,如在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
Run Code Online (Sandbox Code Playgroud)

这是针对C ++的:

a : -0.003564424114301801
b : 0.392436385154724120
c : 0.000000000179659565
d : -0.000000068388217755
e : 0.029652265831828117
r : 0.418524146080017090
Run Code Online (Sandbox Code Playgroud)

他们像打印 printf("a : %.18f\n",a);

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

另外,我观察到一个奇怪的事情:我在一个新的“纯”项目(对于C和C ++)中运行了精确的方程式,即仅运行主体本身。元素的值与上面的元素相同(在float中)。结果是r : 0xD148D63E两者兼而有之。与@geza的注释相同。

Mic*_*ler 15

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

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

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

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

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

注意:根据C ++标准,这实际上不是错误。但是,根据GCC的文档,这是一个错误,该文档声称它遵循Intel体系结构上的IEEE-754 FP标准(就像在许多其他体系结构上一样)。显然,错误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);
} 
Run Code Online (Sandbox Code Playgroud)

支持功能的实现:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  result ( 0.4185241461) : 0xCC48D63E
 result2 ( 0.4185241759) : 0xCD48D63E
Run Code Online (Sandbox Code Playgroud)

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

float a = hex2float(0x1D9969BB);
float b = hex2float(0x6CEDC83E);
float c = hex2float(0xD2DC92B3);
float d = hex2float(0xA61FD930);
float e = hex2float(0x4FE9F23C);
Run Code Online (Sandbox Code Playgroud)

使用这些值,结果输出为:

   result ( 0.4185242951) : 0xD148D63E
  result2 ( 0.4185242951) : 0xD148D63E
Run Code Online (Sandbox Code Playgroud)


Eri*_*hil 5

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

无论出于何种原因,看来您的编译器都选择在不同情况下使用此自由来对表达式进行不同的评估(这可能与所编译的语言有关,或者由于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;
Run Code Online (Sandbox Code Playgroud)