コンパイラが融合乗算/加算を使用して記述されたドット積を実装し、dot({a,c}, {b,d}) := a*b + c*d
fl(⋅ + fl(⋅)) を記述したかのように実装することは適切ですfma(a,b, c*d)
か? 一般的にいいえ!
以下は、IEEE 754 に関する W. Kahan の講義ノートから抜粋した例です。
二乗の差 ² − ² を評価したいとします。
これは、ドット積として記述できますdot({x,y}, {-x,y}) = x*x - y*y
。この単純な定式化は、 ≈ の場合に壊滅的な相殺の影響を受けますが、少なくとも = の場合、 fl(fl(²) − fl(²)) = fl(fl(²) − fl(²)) = fl( であるため、確実にゼロを返します。 0) = 0。
これは、代わりに FMA as で計算できますfma(x,x, -y*y)
。しかし、= = fl(1.234) = 0x1.3be76c8b43958p+0 の場合、結果は、IEEE 754 バイナリ 64 演算では、期待したゼロではなく、−1.3532e7b3d8ep−55 ≈ −3.352 × 10⁻¹⁷ になります。
それは非ゼロであるだけでなく、負でもあるため、上流で ≥ を保証できたとしても、下流で平方根を取得しようとすると NaN に遭遇します。
(因数分解(x + y)*(x - y)
は、もちろん、中間的な壊滅的な相殺を回避するために有効ですが、この質問は、追加の仮定なしで内積を評価することに関するものでした。)
複素積を直角座標 ( + )⋅( + ) = ( − ) + ( + ) で評価したいとします。
この虚数部は、内積 として記述できますdot({a,d}, {b,c}) = a*d + b*c
。代わりに FMA as で計算できますfma(a,d, b*c)
。
複素数 + とその複素共役 − の積は実数であり、虚部はゼロであると予想するかもしれませa*d + b*c
んfma(a,d, b*c)
。たとえば、 = fl(1.234) = 1.3be76c8b43958p+0 かつ = fl(5.678) = 1.6b645a1cac083p+2 の場合、 fl(⋅(−) + fl(⋅)) = −1.6f6512a94ffp−55 ≈ 3.983 × 10⁻ ¹⁷。
したがって、これらのシナリオで FMA を使用するのは、コンパイラの形式としては不適切でありfma(a,b, c*d)
、fma
関数 fromを使用して記述するか、そのような悪ふざけを許可するため<math.h>
に追加することによって、明示的に要求する必要があります。#pragma STDC FP_CONTRACT ON
とは言うものの… GCC 10.2にvfmadd231sd を単に渡すだけで悪用するよう説得するのは難しくないようです。これは、バグのあるオプティマイザーのように見えます。対照的に、Clang 11.0.1 は vfmadd231sd を とともに使用しますが、プラグマを省略したり に設定したりしません。-O2 -march=haswell
#pragma STDC FP_CONTRACT OFF
#pragma STDC FP_CONTRACT ON
OFF