게임 개발을 하다보니, 플레이어의 위치, 타이밍, 걸리는 시간 등 정확한 계산을 요하는 일이 많이 생긴다.
특히나 플레이어간의 동기화를 고려하면, 사소한 오차로 클라이언트간의 소통오류로 이어질수가 있다. 그래서 float type을 사용할 때, 조금 더 주의를 해줘야한다. 바로 부동 소수점 오차, 즉, 플로팅 연산 오류가 생길수 있기 때문이다.
간단하게 개념만 이해한다면, float으로 계산을 하다보면, 가끔씩 소수점 하위 부분에서 각 하드웨어(cpu)의 연산 방식, 성능 등에 따라서, 오류가 생기기도 하는 데, 예를 들어, (float type) * (float type) 의 결과 값이 10f가 나와야 한다면, 9.999999999f가 나온다는 것이다.
왜 이런 일이 벌어지는 것일까?
모든 십진수들이 floating point number형태로 저장이 될수가 없기 때문이다. floating point number는 IEEE-754 표준으로 값이 저장이 된다. 32비트 기준으로 1(부호) + 8(2의 승수) + 23(소숫점 아래 숫자)로 수를 표현하고 저장한다.
'정수 + 소수'방식이 아니라 '지수 * 가수'방식으로 숫자를 2진법으로 변환한뒤 정수부분이 1의 자리+소수점으로 남을때까지 2로 나누고 나눈만큼 지수로 곱해주어 계산한다.
계산법 (쉽게 시원하게 알려주는 곳이 없어서 여기저기 다찾아봤다..)
예를 들어, 10000을 floating point number로 전환한다고 가정하자.
-> 우선, 이진법으로 바꾸자 10011100010000
-> 이를 2^n * 1.xxxx 형태로 바궈보자 => 2^13 * 1.0011100010000 (정규화)
-> 위에서, floating 넘버는 32bit로 지수가 8비트가 있다고 했다. 그래서 지수부에는 지수 13을 이진법으로 바꿔서 넣어준다.. 일것 같지만, 8비트에 -127~ 128까지 표현이 가능하지만, 현재 부호가 없으므로 13에 127을 더한 값을 저장해준다. (바이어스 법) 140을 이진법으로 변환하여, 지수 비트 영역에 넣어준다.
우리가 소숫점을 찍고 디버깅을 했을 때, 0.3, 12.4 등 정확하게 나오는 이유는 c#에서 ToString을 할때 round off error무시한 반올림 숫자가 반환되기 때문이다.
어떻게 방지를 해야하나
float의 int 캐스팅은 되도록 피하도록 하자. 애초에 int 형태로 계산을 할 수가 있으면 좋겠지만, 세상살이 항상 원하는 것만 하면서 살수 없는 노릇 아닌가. 소숫점 연산이 필요할 때, 두가지로 해결할수가 있다. double로 소수점의 정밀도를 높이거나 Epsilon을 사용하도록 하자.
Epslion?
부동소수점 연산에서 반올림 오차와 같은 작은 부동소수점 값들을 처리하는 데 사용되는 매우 작은 값이다. 부동소수점 연산의 정확도 한계나 부정확성을 고려하여 사용 할수가 있다.
float a = 2f;
float b = 2f;
float epsilon = 1E-6;
if (Math.Abs(a - b) < epsilon)
{
Console.WriteLine("a and b are approximately equal.");
}
요점
float값의 실상은 우리가 의도한 값과 다를수가 있다. 연산 방식의 차이 때문이다. 그러니 정 float타입의 값을 사용하고 값의 정확도 비교를 할 떈, epsilon을 사용하거나, double타입으로 소수점의 정밀도를 높이도록 하자.
'Programming > C#' 카테고리의 다른 글
비동기 프로그래밍 - aync void를 지양하자 + (UniTaskVoid는?) (0) | 2023.12.01 |
---|---|
c++ -> c#인터프리터 정리 (0) | 2023.07.03 |
Effective C# - item 1 ~ 10 (0) | 2022.10.03 |
Effective C# - 제네릭의 활용) 타입 매겨변수가 IDispoable을 구현할 경우를 대비해 제네릭을 만든다. (0) | 2022.09.11 |
Effective C# - 제네릭의 활용) 런타임에 타입을 확인하여 최적의 알고리즘을 사용하자. (0) | 2022.09.11 |