Создание 32-разрядного числа с плавающей запятой из 4 составных байтов
Я пытаюсь построить 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 ответов
Вы могли бы использовать 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
.)
Следующие функции упаковывают / распаковывают байты, представляющие значение с плавающей запятой одинарной точности в / из буфера в сетевом порядке байтов. Только метод 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;
}
Ты можешь использовать 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
s в этом решении не нарушают строгие правила алиасинга).
Это все еще зависит от float
Он имеет ширину четыре байта и полагается на то, что ваши четыре байта являются действительным числом с плавающей запятой в формате с плавающей запятой вашей реализации, но вы должны либо сделать эти предположения, либо вы должны написать специальный код обработки, чтобы выполнить преобразование.
Это невозможно сделать, так как разные платформы могут использовать:
- Различный порядок байтов
- различные представления для значений с плавающей точкой (см. пример http://en.wikipedia.org/wiki/IEEE_754-1985)
- разные размеры для значений с плавающей точкой
Мне также интересно, откуда вы берете эти 4 байта?
Если я предполагаю, что вы получаете их из другой системы, и вы можете гарантировать, что обе системы используют один и тот же метод для хранения значений с плавающей запятой в памяти, вы можете использовать трюк объединения. В противном случае ваш код почти гарантированно не будет переносимым.
Я обычно использую это в C - нет memcpy
или union
требуется. Это может нарушить правила псевдонимов в C++, я не знаю.
float bytesToFloat(uint8_t *bytes, bool big_endian) {
float f;
uint8_t *f_ptr = (uint8_t *) &f;
if (big_endian) {
f_ptr[3] = bytes[0];
f_ptr[2] = bytes[1];
f_ptr[1] = bytes[2];
f_ptr[0] = bytes[3];
} else {
f_ptr[3] = bytes[3];
f_ptr[2] = bytes[2];
f_ptr[1] = bytes[1];
f_ptr[0] = bytes[0];
}
return f;
}
Если у вас есть целый массив байтов, который необходимо повторно интерпретировать как числа с плавающей запятой, вы можете при необходимости вызвать следующую процедуру для каждой последовательной последовательности из 4 байтов в массиве, чтобы изменить порядок байтов (например, если вы работаете на машина с прямым порядком байтов, но байты идут в порядке обратного порядка байтов). Затем вы можете просто использоватьuint8_t *
указатель массива на float *
, и доступ к памяти как массиву с плавающей запятой.
void switchEndianness(uint8_t *bytes) {
uint8_t b0 = bytes[0];
uint8_t b1 = bytes[1];
uint8_t b2 = bytes[2];
uint8_t b3 = bytes[3];
bytes[0] = b3;
bytes[1] = b2;
bytes[2] = b1;
bytes[3] = b0;
}
Если вам нужен переносимый способ сделать это, вам придется написать немного кода, чтобы обнаружить последовательность системы.
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;
}