2014-01-08 4 views
6

Я использую 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 позже)?

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); 

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

+1

Причина '(int) f >> 24' возвращает' 0' в том, что 'int' casted' f' равен '0' в первую очередь: литой посылает поплавок на его пол. Это неопределенное поведение, но для этого хакерский способ вам понадобится нечто вроде '* (int *) & f >> 24'. –

ответ

17

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

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 на принимающей стороне, хотя, так как вы можете не относиться к произвольному массиву символов как поплавок! Пример:

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]); 
} 
+0

глупый вопрос, почему «f» после 0,6? Я видел это раньше, просто никогда не видел причины ... – mFeinstein

+0

Мне нравится этот ответ, но мне стало любопытно, существуют ли какие-либо другие способы выполнения байтового уровня на плаву? – mFeinstein

+0

@mFeinstein '0.6f' является константой типа' float', '0.6' является константой типа' double'. Константа типа 'double' будет конвертироваться в' float' автоматически в любом случае (но наивные компиляторы могут генерировать худший код для 'f = 0.6', а некоторые платформы могут быть разными). Существуют и другие способы доступа к байтовому уровню, но «memcpy» - лучший способ здесь. – Gilles

0

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

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

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

-2

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

+0

На самом деле, нет, злоупотребление профсоюзом таким образом небезопасно. Если вы пишете члену союза, вам не разрешается читать обратно с другого члена (это неопределенное поведение). Компиляторы используют это ограничение для оптимизации. – Gilles

+0

Бык. Это целая цель ** union ** ... поместить _in_ данные в один формат и вытащить _out_ неизмененные биты в качестве другого формата. –

+1

Нет, это не цель союза. Целью объединения является использование одного и того же фрагмента памяти для хранения разных данных в разное время. В C89 поведение определяется реализацией. В C++ GCC использует это для оптимизации; Я думал, что некоторые версии также сделали это на C, но, посмотрев, я ошибся: это безопасно с GCC. [C11 также изменился, чтобы сделать это определено] (http://stackoverflow.com/questions/11373203/accessing-inactive-union-member-undefined/11996970#11996970), поэтому, вероятно, на практике это возможно на современных компиляторах, даже если они не полностью совместимы с C11. – Gilles

0

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

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

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

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

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

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

+0

Это будет очень громоздко для моих нужд, так как у меня есть микроконтроллер для получения данных, а производительность не самая лучшая вокруг – mFeinstein

1

Язык 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 floating point formats, но вам, возможно, придется переключать принцип.


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

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

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

+0

Возможно ли сделать в одной строке без 'for'? используя '<<' и '|' для разделения байтов? – mFeinstein

+0

@mFeinstein '<<' и другие целые операции вам не помогут. См. Мое редактирование. – Gilles

+0

Да, я вижу, но я думал, что приведение float в массив без знака может сделать трюк. Я просто пытаюсь избежать ненужного кода, так как это будет работать в прерывании. Но если он начнет усложнять, поэтому memcpy будет почти таким же, и я буду использовать его. – mFeinstein

7

В стандарте 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(); 
} 

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

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

EDIT.

Я объясню, почему этот подход справедлив в 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)] Содержание объекта можно получить с помощью символьного типа:

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

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

A discussion in google groups
Type-punning

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

Еще одна деталь стандартной C99:

  • [6.5.2.3 (3) Сноска 82]типа каламбурная разрешена:

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

+2

Я хотел бы отметить, что, хотя это может быть справедливо в C (C99, я думаю, но не C89?), Это было бы неопределенным поведением в C++. На всякий случай, если все участники C++ пройдут мимо и увидели это. –

+0

@KerrekSB: Да, мой код является стандартным для C99. Я не очень уверен, стандартная ли технология объединения на C89. ('Int' внутри цикла' for' действителен только в C99). Однако вопрос имеет тег 'c', но не' C++ '. – pablo1977

+0

Да, конечно, это было просто замечание. Иногда люди думают, что могут применять вещи с одного языка на другой, поэтому я просто хотел быть в безопасности. C гораздо более расслаблен в доступе к памяти, чем C++; У меня нет стандартной ссылки, в которой говорится, что это действительно, но я надеюсь, что вы правы. –

Смежные вопросы