Преобразование double в float без использования режима округления FPU - PullRequest
5 голосов
/ 06 января 2010

Есть ли у кого-нибудь пригодные фрагменты кода для преобразования IEEE 754 double в непосредственно низшее (соответственно старшее) float, без изменения или предположения о текущем режиме округления FPU ?

Примечание: это ограничение, вероятно, подразумевает, что FPU вообще не используется.Я ожидаю, что самый простой способ сделать это в этих условиях - это прочитать биты двойного в 64-битной длине и работать с этим.

Вы можете принять порядковый номер по вашему выбору для простоты, и эторассматриваемый двойник доступен через поле d в объединении ниже:

union double_bits
{
  long i;
  double d;
};

Я бы попытался сделать это сам, но я уверен, что я внесу трудно заметные ошибки для денормализованных или отрицательныхчисло.

Ответы [ 3 ]

3 голосов
/ 07 января 2010

Я думаю, что работает следующее, но сначала я выскажу свои предположения:

  • числа с плавающей точкой хранятся в формате IEEE-754 в вашей реализации,
  • без переполнения,
  • У вас есть nextafterf() доступно (это указано в C99).

Также, скорее всего, этот метод не очень эффективен.

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

int main(int argc, char *argv[])
{
    /* Change to non-zero for superior, otherwise inferior */
    int superior = 0;

    /* double value to convert */
    double d = 0.1;

    float f;
    double tmp = d;

    if (argc > 1)
        d = strtod(argv[1], NULL);

    /* First, get an approximation of the double value */
    f = d;

    /* Now, convert that back to double */
    tmp = f;

    /* Print the numbers. %a is C99 */
    printf("Double: %.20f (%a)\n", d, d);
    printf("Float: %.20f (%a)\n", f, f);
    printf("tmp: %.20f (%a)\n", tmp, tmp);

    if (superior) {
        /* If we wanted superior, and got a smaller value,
           get the next value */
        if (tmp < d)
            f = nextafterf(f, INFINITY);
    } else {
        if (tmp > d)
            f = nextafterf(f, -INFINITY);
    }
    printf("converted: %.20f (%a)\n", f, f);

    return 0;
}

На моей машине он печатает:

Double: 0.10000000000000000555 (0x1.999999999999ap-4)
Float: 0.10000000149011611938 (0x1.99999ap-4)
tmp: 0.10000000149011611938 (0x1.99999ap-4)
converted: 0.09999999403953552246 (0x1.999998p-4)

Идея состоит в том, что я преобразовываю значение double в значение float - это может быть меньше или больше двойного значения в зависимости от режима округления. При преобразовании обратно в double мы можем проверить, меньше ли оно или больше исходного значения. Затем, если значение float не в правильном направлении, мы смотрим на следующее float число из преобразованного числа в направлении исходного числа.

3 голосов
/ 06 января 2010

Чтобы выполнить эту работу более точно, чем просто заново объединить мантиссу и бит экспоненты, проверьте это:

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

привет

2 голосов
/ 29 октября 2013

Я разместил код для этого здесь: https://stackoverflow.com/q/19644895/364818 и скопировал его ниже для вашего удобства.

    // d is IEEE double, but double is not natively supported.
    static float ConvertDoubleToFloat(void* d)
    {
        unsigned long long x;
        float f; // assumed to be IEEE float
        unsigned long long sign ;
        unsigned long long exponent;
        unsigned long long mantissa;

        memcpy(&x,d,8);

        // IEEE binary64 format (unsupported)
        sign     = (x >> 63) & 1; // 1
        exponent = ((x >> 52) & 0x7FF); // 11
        mantissa = (x >> 0) & 0x000FFFFFFFFFFFFFULL; // 52
        exponent -= 1023;

        // IEEE binary32 format (supported)
        exponent += 127; // rebase
        exponent &= 0xFF;
        mantissa >>= (52-23); // left justify

        x = mantissa | (exponent << 23) | (sign << 31);
        memcpy(&f,&x,4);

        return f;
    }
...