Как получить байты float?
Я использую HIDAPI для отправки некоторых данных на USB-устройство. Эти данные могут быть отправлены только как массив байтов, и мне нужно отправить некоторые номера float внутри этого массива данных. Я знаю, что поплавки имеют 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 байтах char и перестроить тот же поплавок позже)?
EDIT:
я смог выполнить это со следующими кодами:
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 () - это" более чистый " способ сделать это, но таким образом я думаю, что производительность несколько лучше.
6 ответов
вы можете сделать это так:
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 <stdio.h>
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
bits (целочисленная константа >= 8, почти в случаях 8). -
[6.2.6.1(3)] на
unsigned char
type использует все свои биты для представления значения объекта, который является неотрицательным целым числом, в чистом двоичном представлении. Это означает, что нет бит заполнения или бит, используемых для любого другого extrange purpouse. (То же самое не гарантируется для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)] указатель на a объект структуры (в частности, объединения), соответствующим образом преобразованный, указывает на его исходный член. (Таким образом, в начале объединения нет байтов заполнения).
- [6.5(7)] к содержимому объекта можно получить доступ с помощью символьного типа:
объект должен иметь свое сохраненное значение, доступ только к выражению lvalue, которое имеет одно из следующие типы:
- тип, совместимый с действующим тип объект,
- квалифицированная версия типа, совместимого с эффективным типом объекта,
- тип, который является подписанным или неподписанным типом, соответствующим действующему типу объект,
- тип, который является знаковым или беззнаковым типом, соответствующим квалифицированным версия эффективный тип объекта,
- тип агрегата или объединения, который включает один из вышеупомянутых типов среди своих члены (включая, рекурсивно, amember субагрегата или contained union), или
- тип характера
дополнительная информация:
обсуждение в группах google
Type-punning
Изменить 2
еще одна деталь стандарта C99:
- [6.5.2.3 (3) сноска 82] Type-punning разрешается:
если член привык доступ к содержимому объекта объединения не совпадает с содержимым последнего элемента сохраните значение в объекте, соответствующая часть представления объекта значения будет переинтерпретирована как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый " тип каламбур.)" Это может быть представление ловушки.
язык C гарантирует, что любое значение любого type1 может быть доступно в виде массива байтов. Тип байт 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 в функцию записи на USB и указать, сколько байтов нужно скопировать (sizeof(f)
). Вам понадобится явное приведение, если функция принимает аргумент указателя, отличный от void*
.
int write_to_usb(unsigned char *ptr, size_t size);
result = write_to_usb((unsigned char*)f, sizeof(f))
отметим, что это будет работать только если устройство использует то же представление чисел с плавающей запятой, который является общим, но не универсальным. Большинств машины используют форматы с плавающей запятой IEEE, но вам может потребоваться переключить endianness.
а что с твоей попытки:>>
оператор работает на целых числах. В выражении (int) f >> 24
, f
бросается в int
; если бы вы писали f >> 24
без приведения, f
все равно будет автоматически преобразован в int
. Преобразование значения с плавающей запятой в целое число аппроксимирует его путем усечения или округления (обычно в направлении 0, но правило зависит от платформы). 0.6 округлено до целого числа 0 или 1, поэтому data[0]
равно 0 или 1, а остальные-0.
вам нужно действовать на байты объекта float, а не на его значение.
1 исключая функции, которыми на самом деле нельзя манипулировать в C, но включая указатели функций, функции которых распадаются автоматически.
предполагая, что оба устройства имеют одинаковое представление о том, как представлены поплавки, почему бы просто не сделать memcpy
. я.е
unsigned char payload[4];
memcpy(payload, &f, 4);
самый безопасный способ сделать это, если вы контролируете обе стороны, это отправить какое-то стандартизированное представление... это не самый эффективный метод, но он не так уж плох для малых чисел.
hostPort writes char * "34.56" byte by byte
client reads char * "34.56"
затем преобразуется в float с функцией библиотеки atof
или atof_l
.
конечно, это не самый оптимизированный, но это, конечно, будет легко отлаживать.
если вы хотите получить более оптимизированный и творческий, первый байт-это длина, затем показатель, затем каждый байт представляет собой 2 знака после запятой... так что
34.56
становится char array[] = {4,-2,34,56};
что-то вроде этого было бы портативным... Я бы просто попытался не передавать двоичные представления float... потому что это может стать грязным быстро.
было бы безопаснее объединить массив float и char. Поместите в элемент float, вытащите 4 (или любую длину) байта.