与 Linux 相比,Windows/MSYS2 上的双重初始化错误

问题描述

这个简单的程序在 Windows/MSYS2 上给出了错误的结果。在 Ubuntu 18.04 上,它可以正常工作:

#include <math.h>
#include <stdio.h>

int main(int argc,char *argv[]) {
  double a = -1.5708;
  double c = cos(-1.5708);
  double d = cos(a);
  unsigned long long *lc = (unsigned long long *)&c;
  unsigned long long *ld = (unsigned long long *)&d;
  printf("c = %.17e %llu\n",c,*lc);
  printf("d = %.17e %llu\n",d,*ld);
  if (c == d)
    printf("correct result\n");
  else
    printf("wrong result\n");
  return 0;
}

Windows/MSYS2:

$ gcc cos.c -o cos -lm ; ./cos
c = -3.67320510334657393e-06 13749155573439758092
d = -3.67320510334657563e-06 13749155573439758096
wrong result

Ubuntu 18.04:

olivier@ubuntu:~$ gcc cos.c -o cos -lm ; ./cos
c = -3.67320510334657393e-06 13749155573439758092
d = -3.67320510334657393e-06 13749155573439758092
correct result

我做错了什么,是 MSYS2 上 gcc 的错误还是其他什么?

解决方法

对于跨系统的除 sqrt 以外的浮点函数,不能期望获得按位相同的结果。

更具体地说,cos 不能保证正确舍入,因此运行时库的结果(您的第二个结果 d)不一定与编译期间发生的常量折叠的结果相同(您的结果 c)。

例如,GCC FP Math 上的页面指出,在常量折叠期间,所有操作都被正确舍入(甚至是超越函数),但由于效率原因,运行时库通常不是这种情况。 (此外,并非所有编译器都会为您提供正确舍入的常量折叠保证,因此对于交叉编译器或使用与生成的可执行文件不同的 libm 的编译器,您可能会看到此问题。)

GCC 内部使用 MPFR 库,该库使用中间任意精度算法实现正确的舍入运算,但在运行时库中这会太慢。

即使对于 linux,您也可能会找到 cos 函数的参数,以便您的 cd 之间存在分歧(除非他们在幕后使用 crlibm)。因此,cd linux 结果相同在我看来是巧合。

如果您在运行时寻找更可预测的 FP 数学结果,您可以查看 crlibm,它是许多浮点函数的有效实现,可提供正确的舍入结果。它比 MPFR 高效得多,但不如标准 libm 函数高效。