我有一个非常奇怪的错误,我花了好几天试图找出来,所以现在我想看看是否有人有任何评论来帮助我理解正在发生的事情。
一些背景知识。我正在开发一个软件项目,它涉及使用Boost 1.45向Python 2.7.1添加C ++扩展,所以我的所有代码都是通过Python解释器运行的。最近,我对代码进行了修改,打破了我们的一个回归测试。这种回归测试可能对数值波动(例如不同的机器)过于敏感,所以我应该解决这个问题。但是,由于这种回归在产生原始回归结果的同一台机器/编译器上出现了中断,因此我将结果的差异追溯到这段数字代码(这与我改变的代码无关):
c[3] = 0.25 * (-3 * df[i-1] - 23 * df[i] - 13 * df[i+1] - df[i+2]
- 12 * f[i-1] - 12 * f[i] + 20 * f[i+1] + 4 * f[i+2]);
printf("%2li %23a : %23a %23a %23a %23a : %23a %23a %23a %23a\n",i,
c[3],
df[i-1],df[i],df[i+1],df[i+2],f[i-1],f[i],f[i+1],f[i+2]);
构造一些数值表。请注意:
所以我克隆了我的源代码树,我编译的两个可执行文件之间的唯一区别是克隆包含一些额外的代码,甚至在这个测试中都没有执行。这让我怀疑它一定是一个内存问题,因为唯一的区别应该是代码存在于内存中...无论如何,当我运行两个可执行文件时,这里是它们产生的差异:
diff new.out old.out
655,656c655,656
< 6 -0x1.7c2a5a75fc046p-10 : 0x0p+0 0x0p+0 0x0p+0 -0x1.75eee7aa9b8ddp-7 : 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.1eaea08b55205p-4
< 7 -0x1.a18f0b3a3eb8p-10 : 0x0p+0 0x0p+0 -0x1.75eee7aa9b8ddp-7 -0x1.a4acc49fef001p-6 : 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.1eaea08b55205p-4 0x1.9f6a9bc4559cdp-5
---
> 6 -0x1.7c2a5a75fc006p-10 : 0x0p+0 0x0p+0 0x0p+0 -0x1.75eee7aa9b8ddp-7 : 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.1eaea08b55205p-4
> 7 -0x1.a18f0b3a3ec5cp-10 : 0x0p+0 0x0p+0 -0x1.75eee7aa9b8ddp-7 -0x1.a4acc49fef001p-6 : 0x1.304ec13281eccp-4 0x1.304ec13281eccp-4 0x1.1eaea08b55205p-4 0x1.9f6a9bc4559cdp-5
<more output truncated>
你可以看到c [3]中的值略有不同,而rhs值中没有一个是不同的。因此,一些相同的输入如何产生不同的输出。我尝试简化rhs表达式,但我做的任何改变都消除了差异。如果我打印&amp; c [3],那么差异就会消失。如果我在两个不同的机器(linux,osx)上运行,我可以访问,没有区别。这是我已经尝试过的:
我尝试在有问题的机器上从gcc 4.1.2切换到gcc 4.5.2,这个特定的,孤立的差异消失了(但回归仍然失败,所以让我们假设这是一个不同的问题)。
我能做些什么来进一步隔离问题吗?为了将来参考,有没有什么方法可以更快地分析或理解这类问题?例如,考虑到我对lhs的描述,即使rhs没有改变,你会得出什么结论?
编辑:
问题完全归因于-ffast-math
。
答案 0 :(得分:4)
您可以更改程序的浮点数据类型。如果你使用float,你可以切换到double;如果c
,f
,df
为双倍,则可以切换为long double(英特尔为80位,sparc为128位)。对于4.5.2,您甚至可以尝试使用_float128
(128位)软件模拟类型。
浮点类型越长,舍入误差就越小。
为什么添加一些代码(甚至未执行)会改变结果?如果代码大小改变,gcc可能以不同方式编译程序。 GCC内部有很多启发式方法,一些启发式方法基于函数大小。所以gcc可能会以不同的方式编译你的函数。
此外,尝试使用标记-mfpmath=sse -msse2
编译项目,因为使用x87(旧版gcc的默认fpmath)为http://gcc.gnu.org/wiki/x87note
默认情况下,x87算术不是64/32位IEEE
PS:如果您对稳定的数字结果感兴趣,则不应使用-ffast-math
- 类似选项:http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Optimize-Options.html
<强>
-ffast-math
强> 设置-fno-math-errno,-funsafe-math-optimizations, -fno-trapping-math,-finite-math-only,-fno-rounding-math,-fno-signaling-nans和fcx-limited-range。此选项可以定义预处理器宏 FAST_MATH 。
任何-O选项都不应打开此选项,因为它可能导致程序的输出错误,这取决于IEEE或ISO规则的确切实现/数学函数的规范。
这部分快速数学可能会改变结果
<强>
-funsafe-math-optimizations
强> 允许优化浮点算法,(a)假设参数和结果有效,(b)可能违反IEEE或ANSI标准。在链接时使用时,它可能包含更改默认FPU控制字或其他类似优化的库或启动文件。
这部分将隐藏用户的陷阱和类似NaN的错误(有时候用户希望完全获取所有陷阱来调试他的代码)
<强>
-fno-trapping-math
强> 编译代码,假设浮点运算无法生成用户可见的陷阱。这些陷阱包括除零,溢出,下溢,不精确结果和无效操作。该选项意味着-fno-signaling-nans。例如,如果依赖于“不间断”的IEEE算法,设置此选项可能允许更快的代码。
这部分快速数学说,编译器可以在任何地方采用默认的舍入模式(某些程序可能是假的):
<强>
-fno-rounding-math
强> 启用转换和优化,假设默认浮点舍入行为。对于所有浮点到整数转换,这是舍入为零,对于所有其他算术截断,舍入到最接近。 ...此选项允许在编译时对浮点表达式进行常量折叠(可能受舍入模式影响)和在符号相关舍入模式下不安全的算术转换。