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

waj*_*waj 18 c double 32bit-64bit

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

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

  return y * z == 8.34214e08 * 1.25823e45;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

oua*_*uah 19

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

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

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

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

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

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


LSe*_*rni 11

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

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

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

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

测试

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 <stdio.h>

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

 return  printf("%s\n", y * z == 8.34214e08 * 1.25823e45 ? "Equal" : "NOT equal!");
}
Run Code Online (Sandbox Code Playgroud)

强制-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.

  • Gcc通过高于-O0的任何优化级别(由于常量折叠)将计算完全优化为"真".人们需要用更复杂的程序来愚弄gcc的常数折叠优化,以测试OP的原始问题. (2认同)

chu*_*ica 5

在编译时完成的浮点计算通常比double在运行时使用的精度更高.C也可以double以更高的long 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
Run Code Online (Sandbox Code Playgroud)

您的结果可能略有不同.