Разбор чисел с плавающей точкой IEEE двойной точности на компиляторе C без типа двойной точности - PullRequest
23 голосов
/ 10 апреля 2011

Я работаю с 8-битным чипом AVR.Для 64-битного double нет типа данных (double просто сопоставляется с 32-битным float).Тем не менее, я буду получать 64-разрядные двойные числа через Serial, и мне нужно будет выводить 64-разрядные двойные числа через Serial.

Как преобразовать 64-разрядное двойное число в 32-разрядное с плавающей запятой и обратно?без приведения? Формат для 32-разрядного и 64-разрядного форматов будет соответствовать стандарту IEEE 754. Конечно, я предполагаю потерю точности при преобразовании в 32-разрядное число с плавающей запятой.

Для преобразования изС 64-разрядного до 32-разрядного числа с плавающей запятой, я пробую это:

// Script originally from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1281990303
float convert(uint8_t *in) {
  union {
    float real;
    uint8_t base[4];
  } u;
  uint16_t expd = ((in[7] & 127) << 4) + ((in[6] & 240) >> 4);
  uint16_t expf = expd ? (expd - 1024) + 128 : 0;
  u.base[3] = (in[7] & 128) + (expf >> 1);
  u.base[2] = ((expf & 1) << 7) + ((in[6] & 15) << 3) + ((in[5] & 0xe0) >> 5);
  u.base[1] = ((in[5] & 0x1f) << 3) + ((in[4] & 0xe0) >> 5);
  u.base[0] = ((in[4] & 0x1f) << 3) + ((in[3] & 0xe0) >> 5);
  return u.real;
}

Для чисел, подобных 1.0 и 2.0, вышеприведенное работает, но когда я тестировал с передачей 1.1 как 64-разрядного двойного, вывод был немного отключен (буквально, не каламбур!), хотя это могло быть проблемой с моим тестированием.Смотри:

// Comparison of bits for a float in Java and the bits for a float in C after
// converted from a 64-bit double. Last bit is different.
// Java code can be found at https://gist.github.com/912636
JAVA FLOAT:        00111111 10001100 11001100 11001101
C CONVERTED FLOAT: 00111111 10001100 11001100 11001100

Ответы [ 4 ]

5 голосов
/ 11 апреля 2011

IEEE определяет пять различных режимов округления , но по умолчанию используется Округление от половины до четного . Итак, у вас есть мантисса в форме 10001100 11001100 11001100 11001100 ... и вы должны округлить ее до 24 бит. Нумерация битов от 0 (самый старший), бит 24 равен 1; но этого недостаточно, чтобы сказать вам, округлять ли бит 23 вверх или нет. Если бы все оставшиеся биты были равны 0, вы бы не округлили, потому что бит 23 равен 0 (четный). Но оставшиеся биты не равны нулю, поэтому вы округляете их во всех случаях.

Некоторые примеры:

10001100 11001100 11001100 10000000 ... (все ноль) не округляются, поскольку бит 23 уже четный.

10001100 11001100 11001101 10000000 ... (все ноль) округляется, поскольку бит 23 нечетен.

10001100 11001100 1100110x 10000000 ... 0001 всегда округляется, поскольку остальные биты не равны нулю.

10001100 11001100 1100110x 0xxxxxxx ... никогда не округляется, поскольку бит 24 равен нулю.

3 голосов
/ 11 апреля 2011

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

double
extend_float(float f)
{
    unsigned char flt_bits[sizeof(float)];
    unsigned char dbl_bits[sizeof(double)] = {0};
    unsigned char sign_bit;
    unsigned char exponent;
    unsigned int  significand;
    double out;

    memcpy(&flt_bits[0], &f, sizeof(flt_bits));
    /// printf("---------------------------------------\n");
    /// printf("float = %f\n", f);
#if LITTLE_ENDIAN
    reverse_bytes(flt_bits, sizeof(flt_bits));
#endif
    /// dump_bits(&flt_bits[0], sizeof(flt_bits));

    /* IEEE 754 single precision
     *    1 sign bit              flt_bits[0] & 0x80
     *    8 exponent bits         flt_bits[0] & 0x7F | flt_bits[1] & 0x80
     *   23 fractional bits       flt_bits[1] & 0x7F | flt_bits[2] & 0xFF |
     *                            flt_bits[3] & 0xFF
     *
     * E = 0   & F  = 0 -> +/- zero
     * E = 0   & F != 0 -> sub-normal
     * E = 127 & F  = 0 -> +/- INF
     * E = 127 & F != 0 -> NaN
     */
    sign_bit = (flt_bits[0] & 0x80) >> 7;
    exponent = ((flt_bits[0] & 0x7F) << 1) | ((flt_bits[1] & 0x80) >> 7);
    significand = (((flt_bits[1] & 0x7F) << 16) |
                   (flt_bits[2] << 8) |
                   (flt_bits[3]));

    /* IEEE 754 double precision
     *    1 sign bit              dbl_bits[0] & 0x80
     *   11 exponent bits         dbl_bits[0] & 0x7F | dbl_bits[1] & 0xF0
     *   52 fractional bits       dbl_bits[1] & 0x0F | dbl_bits[2] & 0xFF
     *                            dbl_bits[3] & 0xFF | dbl_bits[4] & 0xFF
     *                            dbl_bits[5] & 0xFF | dbl_bits[6] & 0xFF
     *                            dbl_bits[7] & 0xFF
     *
     * E = 0    & F  = 0 -> +/- zero
     * E = 0    & F != 0 -> sub-normal
     * E = x7FF & F  = 0 -> +/- INF
     * E = x7FF & F != 0 -> NaN
     */
    dbl_bits[0] = flt_bits[0] & 0x80; /* pass the sign bit along */

    if (exponent == 0) {
        if (significand  == 0) { /* +/- zero */
            /* nothing left to do for the outgoing double */
        } else { /* sub-normal number */
            /* not sure ... pass on the significand?? */
        }
    } else if (exponent == 0xFF) { /* +/-INF and NaN */
        dbl_bits[0] |= 0x7F;
        dbl_bits[1]  = 0xF0;
        /* pass on the significand */
    } else { /* normal number */
        signed int int_exp = exponent;
        int_exp -= 127;  /* IEEE754 single precision exponent bias */
        int_exp += 1023; /* IEEE754 double precision exponent bias */
        dbl_bits[0] |= (int_exp & 0x7F0) >> 4;  /* 7 bits */
        dbl_bits[1]  = (int_exp & 0x00F) << 4;  /* 4 bits */
    }

    if (significand != 0) {
        /* pass on the significand most-significant-bit first */
        dbl_bits[1] |=  (flt_bits[1] & 0x78) >> 3;    /* 4 bits */
        dbl_bits[2] = (((flt_bits[1] & 0x07) << 5) |  /* 3 bits */
                       ((flt_bits[2] & 0xF8) >> 3));  /* 5 bits */
        dbl_bits[3] = (((flt_bits[2] & 0x07) << 5) |  /* 3 bits */
                       ((flt_bits[3] & 0xF8) >> 3));  /* 5 bits */
        dbl_bits[4] =  ((flt_bits[3] & 0x07) << 5);   /* 3 bits */
    }

    ///dump_bits(&dbl_bits[0], sizeof(dbl_bits));
#if LITTLE_ENDIAN
    reverse_bytes(&dbl_bits[0], sizeof(dbl_bits));
#endif
    memcpy(&out, &dbl_bits[0], sizeof(out));

    return out;
}

Я оставил несколько printf строк, но закомментировал их в комментариях в стиле C ++. Вам нужно будет предоставить соответствующие определения для reverse_bytes, LITTLE_ENDIAN и dump_bits. Я не хотел испортить вам все веселье. Записи в Википедии о одинарной точности и двойной точности числа очень хороши.

Если вы собираетесь много работать с числами с плавающей запятой, вам следует прочитать "Что должен знать каждый компьютерный специалист об арифметике с плавающей запятой" Дэвида Голдберга и «Как печатать числа с плавающей запятой точно» от Стила и Уайта. Это две наиболее информативные статьи, когда дело доходит до понимания того, как работают числа с плавающей запятой.

1 голос
/ 11 апреля 2011

Существует только одна полная реализация IEEE754 double в GCC для AVR , о которой я знаю, и вы можете найти ее здесь .

Вам потребуется этот архив, а затем замените avr_f64.c из архива на this one.

Библиотека занимает около 21K флэш-памяти и 310 байтов оперативной памяти.

Оригинальный пост можно найти здесь .Я извлек всю важную информацию из исходного поста и представил ее здесь, поскольку считаю, что для входа на форум необходимо иметь учетную запись.

1 голос
/ 11 апреля 2011

http://www.google.com/search?q=c+convert+ieee+754+double+single

Один из первых результатов таков:

http://www.mathworks.com/matlabcentral/fileexchange/23173

Код показывает, как преобразовать IEEE-754 double в IEEE-754-подобный (1,5,10) плавающий формат. Этот код содержит множество комментариев и упоминает типичные ловушки, в которые вы можете попасть.

Это не совсем то, что вы хотите, но это хорошая отправная точка.

...