컴퓨터에서 소수점을 나타낼 때 두가지 방법이 있다. 고정소수점(fixed point)과 부동소수점(float point) 방식이다.
그러나 고정소수점에서 부동소수점으로, 또는 부동소수점에서 고정소수점으로 변환하기 위해선 복잡한 과정을 거쳐야 한다. C에서 표준으로 자리잡은 float(혹은 double) 표기 방식이 IEEE-754 standard를 따르기 때문이다.
그럼에도 정확도는 다소 떨어질 수 있겠지만, 복잡한 과정을 거치지 않고도 간단하게 변환하는 방법이 있다.
위 그림처럼 FixPtNum이라는 고정소수점을 float 타입으로 변환해야 한다고 해보자. 편의를 위해 고정소수점은 총 8비트에 소수부는 3비트를 할당했다.
만약 FixPtNum을 1을 왼쪽으로 시프트 연산을 소수비트 자리수만큼 한 값으로 나눠주면 어떻게 될까?
1을 소수비트 자리수만큼 시프트 연산을 수행하면, 이론적으로 그대로 '1'이 될 것이다. 왜냐하면 우리가 생각하는 1000은 1.000을 의미하는 것이기 때문이다.
결과적으로 FixPtNum / (1<<3)은 FixPtNum과 동일하게 될 것이다.
그럼 왜 FixPtNum을 '1'로 나누게 되는것일까?
FixPtNum은 실제로는 C에 없는 타입이므로 FixPtNum은 프로그래머가 가상으로 생각하는 타입이며, (float)으로 형변환을 한다고 해도 우리가 원하는 값으로 바뀌지 않는다.
하지만 이 시프트 연산을 통해 얻어진 '1'을 (float)으로 형변환해 나누게 되면, 그 비율은 우리가 원하는 FixPtNum이 되며, 결과적으로 float형으로 변환된 값을 얻을 수 있다.
typedef s_fixed
{
int value;
unsigned int bits;
} t_fixed;
float fixed_to_float(t_fixed input)
{
return ((float)input / (float)(1 << FRACTIONAL_BITS));
}
이번에는 반대로 부동소수점을 고정소수점으로 바꿔보자. (1<<3)을 나눈 결과가 부동소수점 형태의 FloatNum이므로 여기에 다시 (1<<3)을 곱하면 고정소수점 결과가 얻어질 것임을 알 수 있다.
다만 FloatNum의 비트는 32비트를 가지게 되지만, 우리가 원하는 고정소수점 타입은 8비트 형태이므로 8비트 이후의 값들은 모두 버려지게 될 것이다.
따라서 round함수를 적용해 반올림을 하여 가까운 수로 최대한 맞춰주는 작업을 거치면 정확도를 좀더 올릴 수 있게 된다.
t_fixed float_to_fixed(float input)
{
return (t_fixed)(roundf(input * (float)(1 << FRACTIONAL_BITS)));
}