6. IEEE 부동소수점에서 유효숫자 추출


이 문제에 대해선 아예 설명을 추가해달라고 했던데, 설명해줘도 못 알아먹는다는 데 한 표.

그리고, 이 문제는 사실 검증이 좀 어렵다. Visual Studio에서는 IEEE 16비트 부동 소수점은 아예 구현이 되어있지 않기 때문이다.


IEEE에서는 부동소수점을 1.nn \cdot 2^m 형태로 표현한 뒤 유효숫자(mantissa)에서 1을 뺀 부분을 저장한다.

이 유효숫자를 추출하는 것이 문제의 요구사항이다.


그런데, 문제에는 함정이 하나 있다. 리턴값은 unsigned int형이며, 최대값이 2^{23}-1, 2^{10}-1 이라는 점이다.

즉, 유효숫자 부분을 추출한 뒤 값의 변화 없이 unsigned int로 강제 형변환을 해서 읽은 결과를 물어보는 문제이다.


대략 아래와 같이 짜면 된다.


#include <stdio.h>
#include <stdlib.h>

unsigned int GetMantissa32inRAW(float value)
{
    unsigned int ret = *(unsigned int *)(&value);

    return (ret & 0x007fffff);
}

float Conv2Float32inRAW(unsigned int value)
{
    value &= 0x007fffff;
    value |= 0x3f800000;

    return *(float *)(&value);
}

float GetMantissa(float value)
{
    if (value < 0) value = -value;
    if (value == 0.0) return 0.0;
    while (value >= 2.0) value /= 2;
    while (value < 1.0) value *= 2;

    return value;
}

unsigned int ConvMantissa2UINT(float mantissa, int digit)
{
    if (mantissa < 1.0) return 0;
    mantissa -= 1.0;
    unsigned int ret = 0;

    for (int i = 0; i < digit; i++) {
        ret <<= 1;
        mantissa *= 2;
        if (mantissa >= 1.0) {
            ret |= 1;
            mantissa -= 1.0;
        }
    }
    return ret;
}

unsigned int GetMantissa32(float value)
{
    float mantissa = GetMantissa(value);
    // value = 1.xxxxx
   
    // 32비트 부동소수점에서는 Mantissa가 23비트로 표현됨
    return ConvMantissa2UINT(mantissa, 23);
}

unsigned int GetMantissa16(float value)
{
    float mantissa = GetMantissa(value);
    // value = 1.xxxxx

    // 16비트 부동소수점에서는 Mantissa가 10비트로 표현됨
    return ConvMantissa2UINT(mantissa, 10);
}

int main(int argc, char* argv[])
{
    const float nValue = 3.14194989f;

    unsigned int nMantissaInUINT = GetMantissa32inRAW(nValue);
    float nMantissa = Conv2Float32inRAW(nMantissaInUINT);

    printf("-= result in raw perspective =-\n");
    printf("Mantissa32 in UINT = %u\nMantissa32 in float = %f\n\n", nMantissaInUINT, nMantissa);

    printf("-= result in my functions =-\n");
    printf("mantissa of %f = %f\n", nValue, GetMantissa(nValue));
    printf("GetMantissa32(%f) = %u\n", nValue, GetMantissa32(nValue));
    printf("GetMantissa16(%f) = %u\n", nValue, GetMantissa16(nValue));

    printf("GetMantissa32(%f) + GetMantissa16(%f) = %u\n", nValue, nValue, GetMantissa32(nValue) + GetMantissa16(nValue));

    return 0;
}


실행 결과는 아래와 같다.


-= result in raw perspective =-
Mantissa32 in UINT = 4789685
Mantissa32 in float = 1.570975

-= result in my functions =-
mantissa of 3.141950 = 1.570975
GetMantissa32(3.141950) = 4789685
GetMantissa16(3.141950) = 584
GetMantissa32(3.141950) + GetMantissa16(3.141950) = 4790269


여기서 몇 가지를 주목할 필요가 있다.


1. 문제에서 입력값으로 3.14194989를 명시했지만, 애초에 32비트 부동소수점(float)에선 3.141950까지밖에 표현이 안 된다.


2. 유효숫자(Mantissa)인 1.570975는 \frac{3.1494989}{2}이다. 이 의미를 이해하는 것이 문제의 핵심 중 하나다.


3. GetMantissa32()의 결과값은 4789685인데, 이를 이진수로 표현하면 10010010001010110110101B이고,

    GetMantissa16()의 결과값은 584인데, 이를 이진수로 표현하면 1001001000B이다.

    상위 10비트의 값이 동일한데, 이 이유를 아는 것이 이 문제의 또 하나의 핵심이다.


4. 16비트 부동소수점은 실제 구현된 환경을 접하기 힘들어 정상 동작 여부를 검증하기 힘들다.

    여기서는 위 3번을 통해 제대로 구현했다는 것을 확인할 수 있다.



  1. JAFO 2014.10.31 09:27

    float GetMantissa(float value)
    {
    if (value < 0) value = -value;
    if (value == 0.0) return 0.0;
    while (value >= 2.0) value /= 2;
    while (value < 1.0) value *= 2;
    return value;
    }

    오랫만에 인사드려요.
    한가지 궁금한 점이 있어서요.
    위 함수에서
    while (value < 1.0) value *= 2;
    이 부분은 항상 value 값이 1.0 또는 더 큰 값만 넘어 올 것 같은데 확인 부탁 드려요.

    • 그냥 일상적인 보호용 코드입니다.
      코딩 중에 여러모로 확인을 위해 넣어둔 줄인데 굳이 뺄 필요까진 없어서 놔뒀습니다.

      물론, 현재의 코드에선 의미 없습니다. ^^

+ Recent posts