2017-02-09 11 views
3

어제 프로젝트에서 버그를 추적하고있었습니다. 몇 시간이 지난 후 어느 정도 코드를 수정하여 이 : 컴파일 및 실행 후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()에서 많은 것을 볼 수있다. 그래서 해결책이 아닌 "부분적인 해결 방법"이 될 것이다. ("부동 소수점을 사용하지 않습니다"와 같은 솔루션을 피하기 위해,

+0

부동 소수점/산술은 부정확 할 수 있습니다. 이것은 부동 소수점 연산의 잘 알려진 "기능"입니다. – barny

+0

@barny - 알아,하지만 두 개의 동일한 숫자를 빼거나 제로 부동 소수점 산술을 곱하는 것과 같은 일은 완벽하게 정확했다. "Was"- 융합 - 곱셈 - 덧셈이 더 이상 사실이 아니기 때문에 ... 또한 여기서 오류의 규모가 상당히 크다고 생각합니다. 내가 1e-64처럼 sth를 얻는다면 나는이 질문을하지 않을 것이다 ... –

+0

당신의 탐구와 행운을 빕니다. – barny

답변

2

을 더 일반적으로이 정확하게 당신이 우연히 (-ffp-contract=fast를 사용하여 지불하는 가격입니다, 그것은 바로이 예입니다 William Kahan notes in the problems with automatic contraction)

이론적으로 C (C++이 아님)를 사용하고 있고 컴파일러가 C-1999 pragma (즉,)를 gcc를하지, 당신은

#pragma STDC FP_CONTRACT OFF 
// non-contracted code 
#pragma STDC FP_CONTRACT ON 
+0

'-ffp-contract = off'를 사용하여 전체 파일의 압축을 해제하거나 모든 형태의 해결 방법을 구현할 수 있지만 문제는 이와 같은 버그를 찾는 것이 꽤 길다는 것입니다. 그래서 처음부터 문제를 피할 수있는 방법이 있는지 궁금합니다. –

2

는 흥미롭게도, FMA 덕분에, 수레는 MX 사용하고 내 당신에게 연구 및 CoS를 곱하면되었다 반올림 오류를 제공 할 수있다.

fma(r,cos, -r*cos) = theoretical(r*cos) - float(r*cos) 

그래서 어떻게 든 얻을 결과는 때문에 수레의 곱셈, 이론적 (SX, SY)에서 (SX, SY) 계산 (그러나 COS의 계산의 오류를 반올림 차지하지되었다 얼마나 멀리 나타냅니다 죄).

따라서 부동 소수점 반올림과 관련된 불확실성 간격 내에있는 차이 (ex-sx, ey-sy)에 의존 할 수 있습니까?

+0

글쎄, 코드는 단일 비트까지 계산의 정밀도에 대해서는별로 신경 쓰지 않지만,이 특별한 경우에는 실제로 0인지 '다른 것'이든 상관하지 않습니다. 이 거리는 시간 계산에 사용되기 때문에 "0 시간 길이 이동 0"대신 "어리석은 시간에 매우 작은 움직임"을 얻습니다. –