어제 프로젝트에서 버그를 추적하고있었습니다. 몇 시간이 지난 후 어느 정도 코드를 수정하여 이 : 컴파일 및 실행 후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);
}
: 나는 두 비트 - 동일한 쌍의 2 뺄셈을 요구 한으로보기 뭔가 내 관점에서
$ 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)
가 잘못 (I 두 가지를 얻을 것으로 예상 (2 개의 0을 다시) 제곱하고 함께 더합니다 (제로).
문제의 근본 원인은 융합 - 곱셈 - 덧셈 연산을 사용하는 것으로 밝혀졌습니다.이 연산은 행 어딘가에서 결과를 부정확하게 만듭니다 (내 관점에서). 일반적으로 나는 이라는 결과를 줄 것을 약속했지만,이 최적화에 반대하는 것은 아무것도 없다. 그러나이 경우 1.34925e-06은 내가 기대했던 것보다 실제로 멀리 떨어져있다.
테스트 사례는 매우 "취약합니다"- 더 많은 인쇄물 또는 더 많은 어설 션을 사용하면 컴파일러에서 더 이상 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 - 알아,하지만 두 개의 동일한 숫자를 빼거나 제로 부동 소수점 산술을 곱하는 것과 같은 일은 완벽하게 정확했다. "Was"- 융합 - 곱셈 - 덧셈이 더 이상 사실이 아니기 때문에 ... 또한 여기서 오류의 규모가 상당히 크다고 생각합니다. 내가 1e-64처럼 sth를 얻는다면 나는이 질문을하지 않을 것이다 ... –
당신의 탐구와 행운을 빕니다. – barny