为什么两个相似的浮点计算给出两个不同的结果?

时间:2013-02-04 03:00:47

标签: eigen floating floating-point-precision

以下代码通过使用特征向量作为仅容器或简单的C数组来实现相同的计算。它产生一个封闭但不是位到位的等效结果。

最后的数学运算是x * alpha + y * beta

#include <Eigen/Eigen>

int main()
{
  Eigen::VectorXd x(2);
  double* y = new double[2];
  long long int a = 4603016991731078785;
  double ga = *(double*)(&a);
  long long int b = -4617595986472363966;
  double gb = *(double*)(&b);
  long long int x0 = 451;
  long long int x1 = -9223372036854775100;
  x[0] = *(double*)(&x0);
  y[0] = *(double*)(&x0);
  x[1] = *(double*)(&x1);
  y[1] = *(double*)(&x1);
  double r = ga*x[0] + gb*x[1];
  double s = ga*y[0] + gb*y[1];
}

为什么会这样?

使用MSVC和gcc(64位操作系统)时结果不同。

1 个答案:

答案 0 :(得分:1)

这可能是因为一个计算完全在FPU(浮点单元)内完成,具有80位精度,而另一个计算使用部分64位精度(双倍大小)。这也可以在不使用Eigen的情况下进行演示。看看下面的程序:

int main()
{
  // Load ga, gb, y[0], y[1] as in original program
  double* y = new double[2];
  long long int a = 4603016991731078785;
  double ga = *(double*)(&a);
  long long int b = -4617595986472363966;
  double gb = *(double*)(&b);
  long long int x0 = 451;
  long long int x1 = -9223372036854775100;
  y[0] = *(double*)(&x0);
  y[1] = *(double*)(&x1);

  // Compute s as in original program
  double s = ga*y[0] + gb*y[1];

  // Same computation, but in steps
  double r1 = ga*y[0];
  double r2 = gb*y[1];
  double r = r1+r2;
}

如果你在没有优化的情况下编译它,你会看到r和s有不同的值(至少,我在我的机器上看到过)。查看汇编代码,在第一次计算中,将ga,y [0],gb和y [1]的值加载到FPU中,然后计算ga * y [0] + gb * y [1]为完成后,结果存储在内存中。 FPU以80位进行所有计算,但是当结果存储在内存中时,数字被舍入,以便它适合双变量的64位。

第二次计算的方式不同。首先,将ga和y [0]加载到FPU中,相乘,然后舍入为64位数并存储在内存中。然后,将gb和y [1]加载到FPU中,相乘,然后舍入为64位数并存储在内存中。最后,将r1和r2加载到FPU中,添加,舍入为64位数并存储在内存中。这一次,计算机对中间结果进行了舍入,这导致了差异。

对于此计算,舍入具有相当大的影响,因为您正在使用非正规数。

现在,这里有一点我不太确定(如果这是你的问题,我道歉):这与原始程序有什么关系,其中x是一个特征容器?这里的计算如下:来自Eigen的函数被调用以获得x [0],然后ga和来自该函数的结果被加载到FPU中,相乘并存储在临时存储器位置(64位,所以这是圆)。然后将gb和x [1]加载到FPU中,相乘,加到存储在临时存储单元中的中间结果中,最后存储在x中。因此,在原始程序中的r的计算中,ga * x [0]的结果被舍入为64位。也许这样做的原因是浮点堆栈不会在函数调用中保留。