Создание 32-разрядного числа с плавающей запятой из 4 составных байтов - PullRequest
21 голосов
/ 22 октября 2010

Я пытаюсь построить 32-битный float из 4 составных байтов. Есть ли лучший (или более переносимый) способ сделать это, чем с помощью следующего метода?

#include <iostream>

typedef unsigned char uchar;

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}

int main()
{
    std::cout << bytesToFloat(0x3e, 0xaa, 0xaa, 0xab) << std::endl; // 1.0 / 3.0
    std::cout << bytesToFloat(0x7f, 0x7f, 0xff, 0xff) << std::endl; // 3.4028234 × 10^38  (max single precision)

    return 0;
}

Ответы [ 5 ]

28 голосов
/ 22 октября 2010

Вы можете использовать memcpy ( Результат )

float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;

или объединение * ( Результат )

union {
  float f;
  uchar b[4];
} u;
u.b[3] = b0;
u.b[2] = b1;
u.b[1] = b2;
u.b[0] = b3;
return u.f;

Но это не более переносимо, чем ваш код, поскольку нет гарантии, что платформа имеет младший порядок, или float использует двоичный код IEEE32 или даже sizeof(float) == 4.

(Примечание *: как объясняется @ James , в стандарте технически не разрешено (C ++ § [class.union] / 1) обращаться к члену объединения u.f.)

15 голосов
/ 22 октября 2010

Следующие функции упаковывают / распаковывают байты, представляющие значение с плавающей запятой одинарной точности в / из буфера в сетевом порядке байтов. Только метод pack должен принимать во внимание порядок байтов, поскольку метод unpack явно создает 32-битное значение из отдельных байтов, сдвигая их в битах на соответствующую величину, а затем объединяя их. Эти функции действительны только для реализаций C / C ++, которые хранят число с плавающей запятой в 32-битном формате. Это верно для IEEE 754-1985 реализаций с плавающей запятой.

// unpack method for retrieving data in network byte,
//   big endian, order (MSB first)
// increments index i by the number of bytes unpacked
// usage:
//   int i = 0;
//   float x = unpackFloat(&buffer[i], &i);
//   float y = unpackFloat(&buffer[i], &i);
//   float z = unpackFloat(&buffer[i], &i);
float unpackFloat(const void *buf, int *i) {
    const unsigned char *b = (const unsigned char *)buf;
    uint32_t temp = 0;
    *i += 4;
    temp = ((b[0] << 24) |
            (b[1] << 16) |
            (b[2] <<  8) |
             b[3]);
    return *((float *) &temp);
}

// pack method for storing data in network,
//   big endian, byte order (MSB first)
// returns number of bytes packed
// usage:
//   float x, y, z;
//   int i = 0;
//   i += packFloat(&buffer[i], x);
//   i += packFloat(&buffer[i], y);
//   i += packFloat(&buffer[i], z);
int packFloat(void *buf, float x) {
    unsigned char *b = (unsigned char *)buf;
    unsigned char *p = (unsigned char *) &x;
#if defined (_M_IX86) || (defined (CPU_FAMILY) && (CPU_FAMILY == I80X86))
    b[0] = p[3];
    b[1] = p[2];
    b[2] = p[1];
    b[3] = p[0];
#else
    b[0] = p[0];
    b[1] = p[1];
    b[2] = p[2];
    b[3] = p[3];
#endif
    return 4;
}
14 голосов
/ 22 октября 2010

Вы можете использовать std::copy:

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3) 
{ 
    uchar byte_array[] = { b3, b2, b1, b0 };
    float result;
    std::copy(reinterpret_cast<const char*>(&byte_array[0]),
              reinterpret_cast<const char*>(&byte_array[4]),
              reinterpret_cast<char*>(&result));
    return result;
} 

Это позволяет избежать взлома объединения, который технически не разрешен языком.Он также избегает обычно используемого reinterpret_cast<float*>(byte_array), что нарушает строгие правила псевдонимов (любой объект может интерпретироваться как массив char, поэтому reinterpret_cast в этом решении не нарушают строгие правила псевдонимов).

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

4 голосов
/ 22 октября 2010

Нет никакого способа сделать это переносимым, так как разные платформы могут использовать:

  • различное упорядочение байтов (старший или младший)
  • различные представления для значений с плавающей запятой (см. http://en.wikipedia.org/wiki/IEEE_754-1985 для примера)
  • различные размеры для значений с плавающей запятой

Мне также интересно, откуда вы берете эти 4 байта?

ЕслиЯ предполагаю, что вы получаете их из другой системы, и вы можете гарантировать, что обе системы используют точно один и тот же метод для хранения значений с плавающей запятой в памяти, вы можете использовать трюк объединения.В противном случае ваш код почти гарантированно не будет переносимым.

3 голосов
/ 22 октября 2010

Если вам нужен переносимый способ сделать это, вам нужно будет написать немного кода, чтобы определить порядок работы системы.

float bytesToFloatA(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}


float bytesToFloatB(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b3;
    *((uchar*)(&output) + 2) = b2;
    *((uchar*)(&output) + 1) = b1;
    *((uchar*)(&output) + 0) = b0;

    return output;
}

float (*correctFunction)(uchar b0, uchar b1, uchar b2, uchar b3) = bytesToFloatA;

if ((*correctFunction)(0x3e, 0xaa, 0xaa, 0xab) != 1.f/3.f) // horrifying, I know
{
  correctFunction = bytesToFloatB;
}
...