Преобразование заданной мантиссы, показателя степени и знака для плавания? - PullRequest
0 голосов
/ 19 апреля 2020

Мне даны мантисса, показатель степени и знак, и я должен преобразовать их в соответствующий поплавок. Я использую 22 бита для мантиссы, 9 бит для показателя степени и 1 бит для знака.

Я концептуально знаю, как преобразовать их в число с плавающей точкой, сначала вернув показатель обратно на место, а затем преобразовав полученное число обратно в число с плавающей точкой, но у меня возникли проблемы с реализацией этого в C. Я видел эту ветку , но я не мог понять код, и я не уверен, что ответ даже правильный. Может кто-то указать мне верное направление? Мне нужно закодировать его в C

Редактировать: я добился определенного прогресса, сначала преобразовав мантиссу в двоичную, затем скорректировав десятичную точку двоичного файла, а затем преобразовав двоичный десятичный знак обратно в фактическое плавание. Я основал свои функции преобразования на этих двух страницах GeekforGeek ( одна , две ), но кажется, что выполнение всех этих двоичных преобразований - это долгий и трудный путь. Ссылка выше, очевидно, делает это очень маленькими шагами, используя операторы >>, но я не совсем понимаю, как это приводит к плавающей запятой.

Ответы [ 3 ]

1 голос
/ 19 апреля 2020

Вот программа с комментариями, объясняющими декодирование:

#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


//  Define constants describing the floating-point encoding.
enum
{
    SignificandBits = 22,   //  Number of bits in signficand field.
    ExponentBits    =  9,   //  Number of bits in exponent field.

    ExponentMaximum = (1 << ExponentBits) - 1,
    ExponentBias    = (1 << ExponentBits-1) - 1,
};


/*  Given the contents of the sign, exponent, and significand fields that
    encode a floating-point number following IEEE-754 patterns for binary
    floating-point, return the encoded number.

    "double" is used for the return type as not all values represented by the
    sample format (9 exponent bits, 22 significand bits) will fit in a "float"
    when it is the commonly used IEEE-754 binary32 format.
*/
double DecodeCustomFloat(
    unsigned SignField, uint32_t ExponentField, uint32_t SignificandField)
{
    /*  We are given a significand field as an integer, but it is used as the
        value of a binary numeral consisting of “.” followed by the significand
        bits.  That value equals the integer divided by 2 to the power of the
        number of significand bits.  Define a constant with that value to be
        used for converting the significand field to represented value.
    */
    static const double SignificandRatio = (uint32_t) 1 << SignificandBits;

    /*  Decode the sign field:

            If the sign bit is 0, the sign is +, for which we use +1.
            If the sign bit is 1, the sign is -, for which we use -1.
    */
    double Sign = SignField ? -1. : +1.;

    //  Dispatch to handle the different categories of exponent field.
    switch (ExponentField)
    {
        /*  When the exponent field is all ones, the value represented is a
            NaN or infinity:

                If the significand field is zero, it is an infinity.
                Otherwise, it is a NaN.  In either case, the sign should be
                preserved.

            Note this is a simple demonstration implementation that does not
            preserve the bits in the significand field of a NaN -- we just
            return the generic NAN without attempting to set its significand
            bits.
        */
        case ExponentMaximum:
        {
            return Sign * (SignificandField ? NAN : INFINITY);
        }

        /*  When the exponent field is not all zeros or all ones, the value
            represented is a normal number:

                The exponent represented is ExponentField - ExponentBias, and
                the significand represented is the value given by the binary
                numeral “1.” followed by the significand bits.
        */
        default:
        {
            int    Exponent = ExponentField - ExponentBias;
            double Significand = 1 + SignificandField / SignificandRatio;
            return Sign * ldexp(Significand, Exponent);
        }

        /*  When the exponent field is zero, the value represented is subnormal:

                The exponent represented is 1 - ExponentBias, and the
                significand represented is the value given by the binary
                numeral “0.” followed by the significand bits.
        */
        case 0:
        {
            int    Exponent = 1 - ExponentBias;
            double Significand = 0 + SignificandField / SignificandRatio;
            return Sign * ldexp(Significand, Exponent);
        }
    }
}


/*  Test that a given set of fields decodes to the expected value and
    print the fields and the decoded value.
*/
static void Demonstrate(
    unsigned SignField, uint32_t SignificandField, uint32_t ExponentField,
    double Expected)
{
    double Observed
        = DecodeCustomFloat(SignField, SignificandField, ExponentField);

    if (! (Observed == Expected) && ! (isnan(Observed) && isnan(Expected)))
    {
        fprintf(stderr,
            "Error, expected (%u, %" PRIu32 ", %" PRIu32 ") to represent "
            "%g (hexadecimal %a) but got %g (hexadecimal %a).\n",
            SignField, SignificandField, ExponentField,
            Expected, Expected,
            Observed, Observed);
        exit(EXIT_FAILURE);
    }

    printf(
        "(%u, %" PRIu32 ", %" PRIu32 ") represents %g (hexadecimal %a).\n",
        SignField, SignificandField, ExponentField, Observed, Observed);
}


int main(void)
{
    Demonstrate(0, 0, 0, +0.);
    Demonstrate(1, 0, 0, -0.);
    Demonstrate(0, 255, 0, +1.);
    Demonstrate(1, 255, 0, -1.);
    Demonstrate(0, 511, 0, +INFINITY);
    Demonstrate(1, 511, 0, -INFINITY);
    Demonstrate(0, 511, 1, +NAN);
    Demonstrate(1, 511, 1, -NAN);
    Demonstrate(0, 0, 1, +0x1p-276);
    Demonstrate(1, 0, 1, -0x1p-276);
    Demonstrate(0, 255, 1, +1. + 0x1p-22);
    Demonstrate(1, 255, 1, -1. - 0x1p-22);
    Demonstrate(0, 1, 0, +0x1p-254);
    Demonstrate(1, 1, 0, -0x1p-254);
    Demonstrate(0, 510, 0x3fffff, +0x1p256 - 0x1p233);
    Demonstrate(1, 510, 0x3fffff, -0x1p256 + 0x1p233);
}

Некоторые примечания:

  • ldexp - это стандартная C библиотечная функция. ldexp(x, e) возвращает x, умноженное на 2 до степени e.
  • uint32_t - это 32-разрядное целое число без знака. Это определено в stdint.h.
  • "%" PRIu32 предоставляет спецификацию преобразования printf для форматирования uint32_t.
0 голосов
/ 19 апреля 2020

Вот простая программа, иллюстрирующая, как разбить float на его компоненты и как составить значение float из триплета (знак, экспонента, мантисса):

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

void dumpbits(uint32_t bits, int n) {
    while (n--)
        printf("%d%c", (bits >> n) & 1, ".|"[!n]);
}

int main(int argc, char *argv[]) {
    unsigned sign = 0;
    unsigned exponent = 127;
    unsigned long mantissa = 0;
    union {
        float f32;
        uint32_t u32;
    } u;

    if (argc == 2) {
        u.f32 = strtof(argv[1], NULL);
        sign = u.u32 >> 31;
        exponent = (u.u32 >> 23) & 0xff;
        mantissa = (u.u32) & 0x7fffff;
        printf("%.8g -> sign:%u, exponent:%u, mantissa:0x%06lx\n",
               (double)u.f32, sign, exponent, mantissa);
        printf("+s+----exponent---+------------------mantissa-------------------+\n");
        printf("|");
        dumpbits(sign, 1);
        dumpbits(exponent, 8);
        dumpbits(mantissa, 23);
        printf("\n");
        printf("+-+---------------+---------------------------------------------+\n");
    } else {
        if (argc > 1) sign = strtol(argv[1], NULL, 0);
        if (argc > 2) exponent = strtol(argv[2], NULL, 0);
        if (argc > 3) mantissa = strtol(argv[3], NULL, 0);
        u.u32 = (sign << 31) | (exponent << 23) | mantissa;
        printf("sign:%u, exponent:%u, mantissa:0x%06lx -> %.8g\n",
               sign, exponent, mantissa, (double)u.f32);
    }
    return 0;
}

Примечание что вопреки вашему назначению, размер мантиссы составляет 23 бита, а показатель степени имеет 8 битов, что соответствует стандарту IEEE 754 для 32-битных aka одинарной точности float. См. Статью в Википедии о формате с плавающей запятой одинарной точности .

0 голосов
/ 19 апреля 2020

Связанный вопрос - C ++, а не C. Для преобразования между типами данных в C сохраняющих битах используется инструмент объединения. Что-то вроде

union float_or_int {
  uint32_t i;
  float f;
}

float to_float(uint32_t mantissa, uint32_t exponent, uint32_t sign)
{
  union float_or_int result;
  result.i = (sign << 31) | (exponent << 22) | mantissa;
  return result.f;
}

Извините за опечатки, я давно уже не кодировал C ?

...