Visual C++ 2012中的正弦计算不一致?

Smi*_*Smi 8 c c++ assembly visual-c++ x87

请考虑以下代码:

// Filename fputest.cpp

#include <cmath>
#include <cstdio>

int main()
{
    double x;
    *(__int64 *) &x = 0xc01448ec3aaa278di64; // -5.0712136427263319
    double sine1 = sin(x);
    printf("%016llX\n", sine1);
    double sine2;
    __asm {
    fld x
    fsin
    fstp sine2
    }
    printf("%016llX\n", sine2);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用Visual C++ 2012(cl fputest.cpp)编译并执行程序时,输出如下:

3FEDF640D8D36174
3FEDF640D8D36175
Run Code Online (Sandbox Code Playgroud)

问题:

  • 为什么这两个值不同?
  • 是否可以发出一些编译器选项,以便计算出的正弦值完全相同?

Eri*_*hil 9

此问题不是由long double转换为double引起的.这可能是由于sin数学库中的例程不准确.

fsin指令被指定,以产生1 ULP内的结果(在长双格式),用于其范围(每英特尔64和IA-32体系结构软件开发者手册,2011年10月,第1卷,8.3.10)中,廿四内的操作数到最近的模式.在英特尔酷睿i7上,fsin提问者的值-5.07121364272633190495298549649305641651153564453125或-0x1.448ec3aaa278dp + 2,产生0xe.fb206c69b0ba402p-4.我们可以很容易地从这个十六进制看到最后11位是100 0000 0010.这些是从long double转换时将被舍入的位.如果它们大于100 0000 0000,则数字将被四舍五入.他们更伟大.因此,将此long double值转换为double的结果为0xe.fb206c69b0ba8p-4,等于0x1.df640d8d36175p-1和0.93631021832247418590355891865328885614871978759765625.另请注意,即使结果是一个ULP较低,最后11位仍然会大于100 0000 0000并且仍然会向上舍入.因此,在符合上述文档的Intel CPU上,此结果不应有所不同.

相比之下,直接计算双精度正弦,使用理想的sin程序生成正确的圆形结果.该值的正弦值约为0.936310218322474130518571507850442536345812689613335205125234977386747752408151407029920255207213367935161766406793157656197073431715175310538111963213358998482866862347468763562(用Maple 10计算).最接近它的双精度是0x1.df640d8d36175p-1.这是我们通过将fsin结果转换为double 获得的相同值.

因此,差异不是由长双转换引起的; 将long double fsin结果转换为double会产生与理想的双精度sin例程完全相同的结果.

我们没有sin关于提问者的Visual Studio包使用的例程的准确性的规范.在商业图书馆中,允许1个ULP或几个ULP的错误是常见的.观察正弦与双精度值舍入的点的接近程度:距离双精度为.498864 ULP(双精度ULP),因此距离舍入变化的点为.001136 ULP.因此,即使程序中的非常小的不准确sin也会导致它返回0x1.df640d8d36174p-1而不是更接近0x1.df640d8d36175p-1.

因此,我猜想差异的来源是sin例行程序中的一个非常小的不准确性.