正弦结果取决于使用的 C++ 编译器
sine result depends on C++ compiler used
我使用以下两个 C++ 编译器:
- cl.exe : Microsoft (R) C/C++ 优化编译器 版本 19.00.24210 for x86
- g++ : g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
使用内置正弦函数时,我得到了不同的结果。这并不重要,但有时结果对我来说太重要了。这是一个 '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).
我使用以下两个 C++ 编译器:
- cl.exe : Microsoft (R) C/C++ 优化编译器 版本 19.00.24210 for x86
- g++ : g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
使用内置正弦函数时,我得到了不同的结果。这并不重要,但有时结果对我来说太重要了。这是一个 '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).