floating point precision을 완전히 이해하고 있나?

2023. 11. 16. 02:49배움엔 끝이없다/C++

오늘은 모종의 이유로 게임 엔진 스터디에서 floating point precision에 대해 공부했는데,

같이했던 분이 밤에 대충 아래 내용의 카톡을 보내셨습니다.

if (1.1 + 0.1 == 1.2)
    std::cout << "1.1 + 0.1 == 1.2" << std::endl;
else
    std::cout << "1.1 + 0.1 != 1.2" << std::endl; //이게 출력 됨

if (1.1f + 0.1f == 1.2f)
    std::cout << "1.1f + 0.1f == 1.2f" << std::endl; //이게 출력됨
else
    std::cout << "1.1f + 0.1f != 1.2f" << std::endl;

 

소수점끼리는 == 연산을 하면 안돼요라는 말이 떠오르려던 찰나에,

float는 덧셈 결과가 true, double은 false가 나온걸 보고 할 말을 잃었습니다.

 

분명 double이 float보다 precision이 좋은데? 결과가 나오면 반대가 나와야 하는거 아닌가?

 

우선 부동 소수점 표현은 2진수의 가수부와 지수부로 표현되기 때문에, 1.1과 0.1, 1.2 모두 정확하게 표현할 수 없습니다.

그러면 사실 1.1f + 0.1f == 1.2f는,

(1.1f와 유사한 것) + (0.1f와 유사한 것) == (1.2f와 유사한 것) 을 평가한 것이고,

오차들이 어떻게 우연히 맞아 떨어져서 같다고 판단한걸까? 라는 생각에 아래 코드를 실행해 봤습니다.

//main
std::cout << std::setprecision(16); //소수점 16자리까지 출력하라.

std::cout << 1.1f << std::endl;
std::cout << 0.1f << std::endl;
std::cout << 1.2f << std::endl;
std::cout << 1.1f + 0.1f << std::endl;

std::cout<<std::endl;
std::cout << 1.1 << std::endl;
std::cout << 0.1 << std::endl;
std::cout << 1.2 << std::endl;
std::cout << 1.1 + 0.1 << std::endl;

//실행 결과
1.100000023841858
0.1000000014901161
1.200000047683716
1.200000047683716

1.1
0.1
1.2
1.2

 

float에 대한 생각은 맞았던 것 같은데, double은 정확히 1.1, 0.1, 1.2를 표현하고 있었습니다!

그래서 double은 뭔가 다른 소수점 표현 방식을 쓰나 찾아봤는데, 그건 또 아니었습니다.

그래서 혹시 몰라 precision을 32로 키워보니..

std::cout << std::setprecision(32);
//아까랑 똑같은 코드 ..

//출력
1.10000002384185791015625
0.100000001490116119384765625
1.2000000476837158203125
1.2000000476837158203125

1.1000000000000000888178419700125
0.10000000000000000555111512312578
1.1999999999999999555910790149937
1.200000000000000177635683940025

 

double은 두 값이 다르게 나타나고 있었습니다.

그래서 결론은, float의 precision이 매우 낮다, 역시 소수점은 == 연산을 하면 안된다 입니다.

 

이미 알고 있던 내용이었지만, 막상 눈에 보이는 결과가 이상하니 굉장히 당황스러웠습니다.

 마음 편하게 사려면 실수끼리 비교할 때 ==을 사용하지 않는게 좋겠네요.

 

또 추가로 float의 표준인 IEEE 754 에선 NaN과 Inf를 표현하도록 돼있다고 합니다.

그래서인지 0.0/0.0이 가능합니다.

int a = 1 / 0; //에러!
float b = 0.0f / 0.0f; //잘 실행됨, 출력시 -nan
float c = 1.0f / 0.0f; //이것도 잘 됨, 출력시 inf
float d = c - c; //이것도 됨.. 출력 시 -nan

 

이런 일이 안생기게 해야지 이런 것까지 알아야하나 싶어서.. 대충 훑어보기만 했는데,

nan은 지수부가 모두 1이고 가수부가 0이 아닌 수라고 합니다. 

그래서 부호나 값에 대한 제한이 없어서, 부호는 의미가 없고 가수부에 어떻게 nan이 된건지에 대한 정보가 인코딩 될 때도 있다고 하네요.

 

또, nan == nan은 false이지만, inf == inf 는 true를 반환합니다.

이와 관련된 내용 역시 IEEE 754 floating point 문서에 나와있다고 하네요.

완전 자세한 내용은 https://docs.oracle.com/cd/E19957-01/806-3568/ncg_math.html 를,

대략적인 내용은 https://en.wikipedia.org/wiki/IEEE_754 를 참고하면 좋을 것 같습니다.

 

아무튼 실수는 참 실수하기 좋은 것 같아요 ㅎㅎ~

반응형

'배움엔 끝이없다 > C++' 카테고리의 다른 글

Rule of 0/3/5 의 의도를 이해하자  (0) 2023.11.20