Как получить байты с плавающей точкой?

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

float f = 0.6; char data[4]; data[0] = (int) f >> 24; data[1] = (int) f >> 16; data[2] = (int) f >> 8; data[3] = (int) f; 

И позже мне пришлось только:

 g = (float)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]) ); 

Но тестирование показывает, что строки, подобные data[0] = (int) f >> 24; возвращает всегда 0 . Что не так с моим кодом и как я могу сделать это правильно (т. Е. Сломать внутренние данные float в 4 символьных байтах и ​​перестроить один и тот же float позже)?

РЕДАКТИРОВАТЬ:

Я смог выполнить это с помощью следующих кодов:

 float f = 0.1; unsigned char *pc; pc = (unsigned char*)&f; // 0.6 in float pc[0] = 0x9A; pc[1] = 0x99; pc[2] = 0x19; pc[3] = 0x3F; std::cout << f << std::endl; // will print 0.6 

а также

 *(unsigned int*)&f = (0x3F << 24) | (0x19 << 16) | (0x99 << 8) | (0x9A << 0); 

Я знаю, что memcpy () – это «более чистый» способ сделать это, но я думаю, что производительность несколько лучше.

Вы можете сделать это следующим образом:

 char data[sizeof(float)]; float f = 0.6f; memcpy(data, &f, sizeof f); // send data float g; memcpy(&g, data, sizeof g); // receive data 

Чтобы это работало, обе машины должны использовать одни и те же представления с плавающей запятой.


Как было справедливо указано в комментариях, вам необязательно выполнять дополнительные memcpy ; вместо этого вы можете обращаться с f непосредственно как массив символов (любой подписи). Тем не менее, вам все равно придется выполнять memcpy на принимающей стороне, так как вы можете не обрабатывать произвольный массив символов как float! Пример:

 unsigned char const * const p = (unsigned char const *)&f; for (size_t i = 0; i != sizeof f; ++i) { printf("Byte %zu is %02X\n", i, p[i]); send_over_network(p[i]); } 

В стандарте C гарантируется, что к любому типу можно получить доступ к массиву байтов. Прямой способ сделать это, конечно, с помощью профсоюзов:

  #include  int main(void) { float x = 0x1.0p-3; /* 2^(-3) in hexa */ union float_bytes { float val; unsigned char bytes[sizeof(float)]; } data; data.val = x; for (int i = 0; i < sizeof(float); i++) printf("Byte %d: %.2x\n", i, data.bytes[i]); data.val *= 2; /* Doing something with the float value */ x = data.val; /* Retrieving the float value */ printf("%.4f\n", data.val); getchar(); } 

Как вы можете видеть, совсем необязательно использовать memcpy или указатели ...

union подход легко понять, стандартный и быстрый.

РЕДАКТИРОВАТЬ.

Я объясню, почему этот подход действителен в C ( C99 ).

  • [5.2.4.2.1 (1)] У байта есть бит CHAR_BIT (целочисленная константа> = 8, в большинстве случаев это 8).
  • [6.2.6.1 (3)] Тип unsigned char использует все свои биты для представления значения объекта, который является неотрицательным целым, в чистом двоичном представлении. Это означает, что нет никаких битов заполнения или бит, используемых для любой другой экструзионной пурпуры. (То же самое не гарантируется для signed char или char ).
  • [6.2.6.1 (2)] Каждый тип без битового поля представляется в памяти как непрерывная последовательность байтов.
  • [6.2.6.1 (4)] (цитируется) «Значения, хранящиеся в объектах без бит-бит любого другого типа объекта, состоят из бит n × CHAR_BIT, где n - размер объекта этого типа, в байтах. Значение может быть скопирован в объект типа unsigned char [n] (например, memcpy); [...] "
  • [6.7.2.1 (14)] Указатель на объект структуры (в частности, объединения), соответствующим образом преобразованный, указывает на его начальный элемент. (Таким образом, в начале объединения нет байтов заполнения).
  • [6.5 (7)] Доступ к контенту объекта можно получить по типу символа:

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

Дополнительная информация:

Обсуждение в группах google
Type-каламбурная

EDIT 2

Другая деталь стандарта C99:

  • [6.5.2.3 (3) сноска 82] Допускается использование пула :

Если элемент, используемый для доступа к содержимому объекта объединения, не совпадает с элементом, используемым последним для хранения значения в объекте, соответствующая часть представления объекта значения интерпретируется как представление объекта в новом типе как описанный в 6.2.6 (процесс, иногда называемый «пингом типа»). Это может быть ловушечное представление.

Язык C гарантирует, что любое значение любого типа¹ можно получить в виде массива байтов. Тип байтов – это unsigned char . Вот низкоуровневый способ копирования float в массив байтов. sizeof(f) – количество байтов, используемых для хранения значения переменной f ; вы также можете использовать sizeof(float) (вы можете либо передать sizeof переменной, либо более сложное выражение, или его тип).

 float f = 0.6; unsigned char data[sizeof(float)]; size_t i; for (i = 0; i < sizeof(float); i++) { data[i] = (unsigned char*)f + i; } 

Функции memcpy или memmove выполняют именно это (или оптимизированную версию).

 float f = 0.6; unsigned char data[sizeof(float)]; memcpy(data, f, sizeof(f)); 

Вам даже не нужно делать эту копию. Вы можете напрямую передать указатель на float в функцию write-to-USB и указать, сколько байтов копировать ( sizeof(f) ). Вам понадобится явное преобразование, если функция принимает аргумент указателя, отличный от void* .

 int write_to_usb(unsigned char *ptr, size_t size); result = write_to_usb((unsigned char*)f, sizeof(f)) 

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


Что касается того, что не так с вашей попыткой: оператор >> работает с целыми числами. В выражении (int) f >> 24 f передается в int ; если вы написали f >> 24 без трансляции, f все равно автоматически преобразуется в int . Преобразование значения с плавающей запятой в целое число аппроксимирует его, обрезая или округляя его (обычно в направлении 0, но правило зависит от платформы). 0,6, округленное до целого, равно 0 или 1, поэтому data[0] равны 0 или 1, а остальные - 0.

Вам нужно действовать на байты объекта float, а не на его значение.

¹ Исключение функций, которые на самом деле нельзя манипулировать на C, но включая указатели функций, функции которых автоматически распадаются.

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

 unsigned char payload[4]; memcpy(payload, &f, 4); 

самый безопасный способ сделать это, если вы контролируете обе стороны – отправить какое-то стандартизованное представление … это не самый эффективный, но это не так уж плохо для небольших чисел.

 hostPort writes char * "34.56\0" byte by byte client reads char * "34.56\0" 

затем преобразует в float с библиотечной функцией atof или atof_l .

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

если вы хотите получить более оптимизированный и творческий, первый байт – это длина, то показатель, то каждый байт представляет 2 десятичных знака … так что

34.56 становится char array[] = {4,-2,34,56}; что-то подобное было бы переносимым … Я бы просто попытался не передавать двоичные представления с плавающей точкой … потому что он может стать беспорядочным.

Возможно, было бы безопаснее объединять массив float и char. Поместите в поплавковый элемент, вытащите 4 (или любую длину) байтов.