首页 > 解决方案 > C 与 C++ 中的浮点运算差异

问题描述

解决方案

感谢@Michael Veksler 的回答,我走上了寻找解决方案的正确轨道。@Christoph 在这篇文章中建议尝试不同的编译器标志来设置浮点运算的精度。

对我来说,-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++ 中,abcde相等,但结果不同。

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 + bthenr = r - c等,结果是相等的。我有一台 64 位的 Windows 机器。

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

编辑

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

-O0 -g3 -Wall -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++)中运行了精确的方程,即只有主本身。元素的值与上面的相同(浮点数)。结果r : 0xD148D63E对双方都是。与@geza 的评论相同。

标签: c++c

解决方案


简介:鉴于问题不够详细,我只能推测臭名昭著的 gcc 的 323 错误。正如低 bug-ID 所暗示的那样,这个 bug 一直存在。该错误报告自 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上被调用,并且所有关于额外精度和评估顺序的赌注都被取消。

编辑我已经在英特尔 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

推荐阅读