C++ visualc+中的正弦计算不一致性+;2012?

C++ visualc+中的正弦计算不一致性+;2012?,c++,c,visual-c++,assembly,x87,C++,C,Visual C++,Assembly,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

考虑以下代码:

// 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;
}
问题:

  • 为什么这两种价值观不同
  • 是否可以发出一些编译器选项,使计算的正弦值完全相同
(注意:如评论中所述,这在VC2012上不起作用。我把它放在这里是为了一般信息。无论如何,我不建议依赖任何取决于优化级别的东西!)

我没有VS2012,但在VS2010编译器上,您可以在命令行上指定
/fp:fast
,然后得到相同的结果。这会导致编译器生成“快速”代码,不一定完全遵循C++中所需的舍入和规则,而是符合汇编语言计算。 我不能在VS2012中尝试,但我想它也有同样的选择

这似乎只适用于优化的版本,并将
/Ox
作为一个选项。

请参阅

如前所述,差异来自将FP寄存器中的数据移动到不同大小的内存位置(寄存器/ram)。而且也不总是任务;即使是附近的另一个浮点操作也足以刷新FP寄存器,使得任何保证特定值的尝试都是徒劳的。如果需要进行比较,您可以通过将所有结果强制放到内存位置来缓解其中的一些问题,如下所示:

float F1 = sin(a); float F2 = sin(b); if (F1 == F2)
然而,即使这样也可能行不通。最好的方法是接受任何浮点运算都是“最准确的”,并且从程序员的角度来看,即使重复执行相同的操作,该错误也将是不可预测和随机的。而不是

if (F1 == F2)
你应该用一些对你有影响的东西

if (isArbitrarilyClose(F1, F2))


if(absf(F1-F2)此问题不是由长双精度到双精度的转换引起的。它可能是由于数学库中的
sin
例程不准确造成的

指定的
fsin
指令用于对其范围内的操作数(根据《英特尔64和IA-32体系结构软件开发人员手册》,2011年10月,第1卷,8.3.10)生成1 ULP(长双精度格式)以内的结果,以四舍五入到最接近模式。在英特尔Core i7上,提问者的值为
fsin
,−5.07121364272633190495298549649305641651153564453125或-0x1.448EC3AA278DP+2生成0xe.fb206c69b0ba402p-4。我们可以很容易地从这个十六进制中看出,最后11位是100 0000 0010。这些是从长双精度转换时将被舍入的位。如果它们大于100 0000 0000,则数字将被舍入。它们是g因此,将这个长双精度值转换为双精度值的结果是0xe.fb206c69b0ba8p-4,它等于0x1.df640d8d36175p-1和0.936310218322474185903558918653288856614871978759765625。还要注意的是,即使结果比ULP低一个,最后11位仍将大于100 0000,并且仍将向上取整。因此在符合上述说明文件的英特尔CPU上,结果不应发生变化

将此与直接计算双精度正弦进行比较,使用产生正确四舍五入结果的理想
sin
例程。该值的正弦约为0.93631021832247413051857150785044253634581268961333520518023697738674775240815140702992052052057213367935516756640671576565197619707374317175310538111963899828668253520371084906593355262347468763562(使用Maple 10计算)。最接近此值的双精度是0x1.df640d8d36175p-1。这与我们通过将
fsin
结果转换为双精度得到的值相同

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

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


因此,我推测差异的来源是
sin
例程中的一个非常小的错误。

VS2012中的代码生成器已进行了实质性更改以支持。部分更改是x86浮点运算现在在SSE2中完成,不再使用FPU,这是因为FPU代码无法矢量化所必需的。SSE2 com以64位精度而不是80位精度进行计算,由于四舍五入,结果很有可能相差一位。另外,在VS2010中@J99可以获得与/fp:fast一致的结果,其编译器仍然使用FPU,/fp:fast直接使用FSIN结果


这个功能有很多优点,请查看链接url上Jim Hogg的视频,了解如何利用它。

一个操作可以完全在浮点寄存器中进行。另一个操作在将80位寄存器写入64位内存地址时会导致精度损失。FSTP文档说“将值存储在内存中时,该值将转换为单或双实格式。”fsin方法使用80位精度的x87 FPU,MSVC(我使用的是2010)中的sin实现似乎使用SSE及其128位xmm*寄存器。(另请参阅。)证明这是不正确的:在这种情况下,将长双精度
fsin
结果四舍五入到double不会产生不正确的值或与直接计算正确四舍五入的
sin
结果不同的值。浮点计算可能会
if (isArbitrarilyClose(F1, F2))
if (absf(F1 - F2) <= n)