これはおそらく、一方の計算が完全に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ビットで実行しますが、結果がメモリに格納されると、数値は丸められて、double変数の64ビット内に収まります。
2番目の計算は異なる方法で進行します。まず、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ビットに丸められます。おそらくこれの理由は、浮動小数点スタックが関数呼び出し間で保持されないためです。