正弦结果取决于所使用的C ++编译器

我使用以下两个C ++编译器:

  • cl.exe :适用于x86的Microsoft(R)C / C ++优化编译器版本19.00.24210
  • g ++ :g ++(Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010

当使用内置的正弦函数时,我得到不同的结果。 这并不重要,但有时候结果对我的使用来说太重要了。 这是一个“硬编码”值的例子:

printf("%f\n", sin(5451939907183506432.0)); 

结果与cl.exe:

 0.528463 

用g ++的结果:

 0.522491 

我知道g ++的结果更准确,我可以使用额外的库来得到相同的结果,但这不是我的观点。 我真的很明白这里发生了什么事情: cl.exe为什么错了?

有趣的是,如果我在param上应用模(2 * pi),那么我得到的结果与g ++相同。

[编辑]只是因为我的例子看起来疯了你们中的一些人:这是一个伪随机数发生器的一部分。 知道正弦的结果是否准确并不重要,我们只是需要它来给出一些结果。

我认为山姆的评论是最接近的标志。 而使用最新版本的GCC / glibc(它在软件中实现sin()(在编译时计算所涉及的文字),x86的cl.exe可能使用fsin指令。 后者可能是非常不精确的,如随机ASCII博客文章“ 英特尔低估了1.3%的错误界限 ”所述。

特别是您的示例问题的一部分是英特尔在进行范围缩减时使用不精确的pi近似值:

当从双精度(53位尾数) pi进行范围缩减时,结果将具有大约13位的精度(66减53),对于高达2 ^ 40个ULP(53减13)的误差。

你有19位数字,但是双精度通常有15-17位精度。 因此,你可以得到一个小的相对误差(当转换为double时),但是足够大(在正弦计算的情况下)绝对误差。

实际上,标准库的不同实现方式在处理这么多的数字时有所不同。 例如,在我的环境中,如果我们执行

 std::cout << std::fixed << 5451939907183506432.0; 

g ++的结果是5451939907183506432.000000
cl结果将是5451939907183506400.000000

不同之处在于,早于19的cl的版本具有格式化algorithm,该algorithm只使用有限数量的数字并用零填充剩余小数位。

此外,让我们看看这个代码:

 double a[1000]; for (int i = 0; i < 1000; ++i) { a[i] = sin(5451939907183506432.0); } double d = sin(5451939907183506432.0); cout << a[500] << endl; cout << d << endl; 

当我的x86 VC ++编译器执行时,输出是:

 0.522491 0.528463 

看来在填充数组sin被编译为__vdecl_sin2的调用,并且当有单个操作时,它被编译为__libm_sse2_sin_precise (使用/fp:precise )的调用。

在我看来,你的数字对于计算sin来说太大了,以至于不同的编译器会有相同的行为,并期望一般的正确行为。

根据cppreference :

如果arg的大小很大(直到C ++ 11),结果可能几乎没有意义,

这可能是问题的原因,在这种情况下,您将需要手动执行模数,以使arg不大。