昨日私は数時間後に多かれ少なかれ次のようなことをしていたコードに絞り込んだプロジェクトのバグを追跡していましたこの:コンパイルと実行後fused-multiply-add浮動小数点の不正確さを処理する一般的な方法
#include <iostream>
#include <cmath>
#include <cassert>
volatile float r = -0.979541123;
volatile float alpha = 0.375402451;
int main()
{
float sx = r * cosf(alpha); // -0.911326
float sy = r * sinf(alpha); // -0.359146
float ex = r * cosf(alpha); // -0.911326
float ey = r * sinf(alpha); // -0.359146
float mx = ex - sx; // should be 0
float my = ey - sy; // should be 0
float distance = sqrtf(mx * mx + my * my) * 57.2958f; // should be 0, gives 1.34925e-06
// std::cout << "sv: {" << sx << ", " << sy << "}" << std::endl;
// std::cout << "ev: {" << ex << ", " << ey << "}" << std::endl;
// std::cout << "mv: {" << mx << ", " << my << "}" << std::endl;
std::cout << "distance: " << distance << std::endl;
assert(distance == 0.f);
// assert(sx == ex && sy == ey);
// assert(mx == 0.f && my == 0.f);
}
:
$ g++ -Wall -Wextra -Wshadow -march=native -O2 vfma.cpp && ./a.out
distance: 1.34925e-06
a.out: vfma.cpp:23: int main(): Assertion `distance == 0.f' failed.
Aborted (core dumped)
ビュー何かの私の視点から、私は(私は2つを取得することが予想2ビット単位、同一ペアの2つの減算を求めてきたように、間違っていますそれらを二乗(2つのゼロ)し、それらを一緒に(ゼロ)加算することによって、
問題の根本的な原因は、fused-multiply-add演算の使用であることが判明しました。これは、行のどこかで結果が不正確になります(私の観点から)。一般的に私はこの最適化に対して何も持っていません。より正確な結果を出すことが約束されていますので、と正確ですが、この場合1.34925e-06は私が期待していた0から本当に遠いです。
テストケースは非常に「壊れやすい」 - それ以上のプリントを有効にすると、それ以上アサートすると、コンパイラはfused-multiply-addを使用しないためアサートを停止します。
私は、コンパイラにかかわる問題であることを考えられてきたように$ g++ -Wall -Wextra -Wshadow -march=native -O2 vfma.cpp && ./a.out
sv: {-0.911326, -0.359146}
ev: {-0.911326, -0.359146}
mv: {0, 0}
distance: 0
は、私がいることを報告してきたが、これは正しい動作であることを説明して閉じてしまった:たとえば、私は、すべての行のコメントを解除した場合。
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79436
だから私は思ったんだけど - べき一つのコードこのような計算は、問題を回避する方法?私は、一般的な解決策を考えていたが、より良い何か:
mx = ex != sx ? ex - sx : 0.f;
は私が修正したり、自分のコードを改善したいと思います - fused-として、代わりに私のプロジェクト全体のために-ffp-contract=off
を設定する - 改善/修正するために何があるかどうコンパイラライブラリの内部でmultiply-addが使用されています(sinf()とcosf()で多く参照されています)、解決策ではなく「部分的な回避策」になります。 (「浮動小数点を使用しないでください」のようなソリューションを避けるために、
浮動小数点数/算術は不正確である可能性があります。これは浮動小数点演算のよく知られた「機能」です。 – barny
@barny - 私は知っていますが、2つの同一の数値を減算するか、ゼロを浮動小数点演算で何かを掛けるようなものについては、完全に正確でした。 "あった" - 融合多重積算のためにもはやそうではないから...また、ここでの誤差の規模はかなり大きいと思う。もし私が1e-64のようなsthを取得すれば、私はこの質問をしません。 –
あなたの探求の幸運.. – barny