这可能是因为一个计算完全在 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 容器?这里的计算如下:调用 Eigen 中的函数以获取 x[0],然后 ga 并将该函数的结果加载到 FPU 中,相乘并存储在临时内存位置(64 位,所以这是圆角)。然后将 gb 和 x[1] 加载到 FPU 中,相乘,加到存储在临时内存位置的中间结果,最后存储在 x 中。所以在原程序计算r时,将ga*x[0]的结果四舍五入到64位。原因可能是浮点堆栈没有在函数调用中保留。