c++ - 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++ 中,abcd和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
thenr = 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 的评论相同。
解决方案
简介:鉴于问题不够详细,我只能推测臭名昭著的 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
推荐阅读
- node.js - 使用node串口和AT指令发送短信
- c++ - 一直试图在c++中将12小时制格式转换为24小时制格式
- reactjs - 如果复选框处于活动状态,请检查 React JS
- javascript - 如何从为html页面导出的内容部分PDF文件中删除黑色背景颜色
- laravel - 用户验证后如何触发事件?
- xamarin.forms - ReactiveUI 9.19.5 版绑定不适用于 Xamarin.Forms 4.1.0 版
- oracle - 在包中定义集合类型
- javascript - 如何使用正则表达式匹配“exclude=1”形式的字符串?
- git - 无法克隆 git repo
- aframe - 如何在 aframe 中使用 dat.GUI 访问 THREE.TorusKnotGeometry 的参数