双乘法在32位平台的编译时和运行时之间有所不同

我正在32位和64位平台上编译并运行以下程序:

int main() { double y = 8.34214e08; double z = 1.25823e45; return y * z == 8.34214e08 * 1.25823e45; } 

虽然在64位中结果是预期的(值是相同的,退出代码是非零的)在32位似乎在编译时计算的值,比较的右侧和左侧之间存在一点差异在运行时计算的一面。

这是编译器中的错误还是有逻辑解释?

编辑:这与为什么比较双重和浮动导致意外结果不同? 因为这里所有的价值都是双倍的。

IEEE-754允许以更高的精度完成中间计算(强调我的)。

(IEEE-754:2008)“语言标准还应定义并要求实现提供允许和禁止对块进行单独或共同的值更改优化的属性。这些优化可能包括但不限于: […] 在表达评估中使用更广泛的中间结果 。“

在您的情况下,例如在IA-32上,双值可以更高的精度存储在x87 FPU寄存器中(80位而不是64位)。 因此,您实际上将双精度乘法与双展精度乘法进行比较。

例如,在x64上,结果为1 (不使用x87 FPU而不使用SSE),添加gcc选项-mfpmath=387以使用x87使得我的机器上的结果更改为0

如果你想知道C是否也允许这样做,那就是:

(C99,6.3.1.p8)“浮动操作数和浮动表达式的结果的值可以比该类型所要求的更精确和范围表示;”

通常, 永远不要使用浮点数进行等式检查 。 您需要检查所需的结果是否与您获得的结果相差小于预设精度。

这里发生的事情极有可能是由于乘法在两个不同的“平台”上运行:一次是通过代码,一次是编译器,可能有不同的精度。 大多数 编译器都会发生这种情况

如果使用用于编译编译器的相同选项编译它(假设编译器是自己编译的),那么您的程序可能工作。 但这并不意味着你会得到正确的结果; 您将获得编译器获得的相同精度错误。

(另外,我假设编译器执行直接乘法, 并且识别浮点数的解析代码不会进入等式。这可能是我的一厢情愿的想法)。

测试

 Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-suse-linux/4.8/lto-wrapper Target: x86_64-suse-linux Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.8 --enable-ssp --disable-libssp --disable-plugin --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --enable-linker-build-id --enable-linux-futex --program-suffix=-4.8 --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux --host=x86_64-suse-linux Thread model: posix gcc version 4.8.3 20141208 [gcc-4_8-branch revision 218481] (SUSE Linux) #include  int main() { double y = 8.34214e08; double z = 1.25823e45; return printf("%s\n", y * z == 8.34214e08 * 1.25823e45 ? "Equal" : "NOT equal!"); } 

强制-O0避免编译器优化整个代码(感谢@markgz!),我们得到

$ gcc -m32 -O0 -o float float.c && ./float NOT equal! $ gcc -m32 -frounding-math -O0 -o float float.c && ./float Equal

为了记录,因为你在我面前:-),

-frounding,数学

禁用假定默认浮点舍入行为的转换和优化。 对于所有浮点到整数转换,这是零到零,对于所有其他算术截断,这是舍入到最接近的。 应为动态更改FP舍入模式的程序指定此选项,或者可以使用非默认舍入模式执行此选项。 此选项禁用在编译时(可能受舍入模式影响)的浮点表达式的常量折叠以及在存在依赖于符号的舍入模式时不安全的算术转换。

默认值为-fno-rounding-math。

在编译时完成的浮点计算通常以比运行时double使用更高的精度发生。 C也可以在较高的long double精度下执行运行时中间double计算。 要么解释你的不平等。 有关详细信息,请参阅FLT_EVAL_METHOD

  volatile double y = 8.34214e08; volatile double z = 1.25823e45; volatile double yz = 8.34214e08 * 1.25823e45; printf("%.20e\n", y); printf("%.20e\n", z); printf("%.20e\n", yz); printf("%.20Le\n", (long double) y*z); printf("%.20Le\n", (long double) 8.34214e08 * 1.25823e45); 8.34214000000000000000e+08 1.25822999999999992531e+45 // 3 different products! 1.04963308121999993395e+54 1.04963308121999993769e+54 1.04963308122000000000e+54 

您的结果可能略有不同。