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

Nic*_*las 37 c++ precision trigonometry

我使用以下两个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));
Run Code Online (Sandbox Code Playgroud)

cl.exe的结果:

0.528463
Run Code Online (Sandbox Code Playgroud)

使用g ++的结果:

0.522491
Run Code Online (Sandbox Code Playgroud)

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

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

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

DAl*_*Ale 36

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

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

std::cout << std::fixed << 5451939907183506432.0;
Run Code Online (Sandbox Code Playgroud)

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; 
Run Code Online (Sandbox Code Playgroud)

使用我的x86 VC++编译器执行时,输出为:

0.522491
0.528463
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我原以为编译器会通过解释这个文字产生完全相同的位模式,然后就不能解释结果的差异. (4认同)
  • 这是因为libstdc ++尝试显示表示的值(即它将是``n.000000``,其中n被舍入为2的幂).另一方面,MSVCRT最后只会显示零.GNU实现可以说更准确,但是出于实际目的,这些在任何情况下都是非显着的数字,并且不能被依赖. (3认同)
  • @SirGuy可能这个标准没有指定,并且工作受当前舍入设置和编译器标志的影响. (2认同)

小智 16

我认为Sam的评论最接近商标.虽然您使用的是GCC/glibc的近期版本,它在软件中实现了sin()(在编译时为相关文字计算),但x86的cl.exe可能会使用fsin指令.后者可能非常不精确,如随机ASCII博客文章" 英特尔低估错误界限1.3夸张 "中所述.

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

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

  • *"x86的cl.exe可能使用fsin指令"*这似乎是一个非常流行的理论,但它绝对是错误的.除非您明确地将`/ arch:IA32`开关传递给*disable*SSE2支持,否则32位版本的MSVC*假定*SSE2支持可用并生成SSE2指令.它现在已经为许多版本做到了这一点.特别是对于`sin`,它调用它的库函数`___ libm_sse2_sin`或`__libm_sse2_sin_precise`,这取决于你是否设置了`/ fp:fast`或`/ fp:precise`(后者是默认值).这些都不使用`FSIN`(也不是'FCOS`或`FSINCOS`). (7认同)
  • 我认为非常相关的是,在gcc中,编译器正在进行此计算,并且使用cl,它在运行时由代码完成.我知道gcc已经付出了很多努力来确保编译器获得与它生成的代码完全相同的答案.但这迫使他们仔细思考这样的问题,我怀疑cl没有做同样的事情. (3认同)
  • @SloopJon编译器的第15版不是我称之为"现代"的版本.这是VS 2008发布的,现在已经有近10年了.VS 2012(cl.exe版本17)是引入我所说的更改的版本,即使在32位版本上,SSE2也是最低支持的指令集.请注意,提问者正在使用版本19,因此他默认为x86-32版本设置`/ arch:SSE2`,除非他使用`/ arch:IA32`特别禁用它.这就是我最初询问编译器开关的原因. (2认同)

Sir*_*Guy 9

根据cppreference:

如果arg的大小很大,那么结果可能很少或没有意义(直到C++ 11)

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

  • @Joshua为什么不呢?如果一个编译器执行模数而另一个编译器没有这样可以解释结果的差异 (2认同)