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

sine result depends on C++ compiler used

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

使用内置正弦函数时,我得到了不同的结果。这并不重要,但有时结果对我来说太重要了。这是一个 'hard-coded' 值的示例:

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

cl.exe的结果:

0.528463

g++ 的结果:

0.522491

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

有趣的是,如果我对参数应用 (2 * pi) 的模,那么我得到的结果与 g++ 相同...

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

你有一个 19 位的文字,但双精度通常有 15-17 位的精度。结果,您可以获得较小的相对误差(当转换为双精度时),但绝对误差足够大(在正弦计算的上下文中)。

实际上,标准库的不同实现在处理如此大的数字时存在差异。例如,在我的环境中,如果我们执行

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

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

区别是因为 19 之前的 cl 版本有一种格式化算法,它只使用有限数量的数字,并用零填充剩余的小数位。

另外,我们再看这段代码:

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

The result may have little or no significance if the magnitude of arg is large (until C++11)

这可能是问题的原因,在这种情况下,您需要手动进行取模,这样 arg 就不会很大。

我认为 Sam 的评论最贴切。虽然您使用的是 GCC/glibc 的最新版本,它在软件中实现了 sin() (在编译时针对相关文字计算),但 x86 的 cl.exe 可能使用 fsin 指令。后者可能非常不精确,如 Random ASCII 博客 post、“Intel Underestimates Error Bounds by 1.3 quintillion”.

中所述

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

When doing range reduction from double-precision (53-bit mantissa) pi the results will have about 13 bits of precision (66 minus 53), for an error of up to 2^40 ULPs (53 minus 13).