Преобразование 4 необработанных байтов в 32-битную с плавающей запятой

Я пытаюсь реконструировать 32-битное значение с плавающей запятой из eeprom.

4 байта в памяти eeprom (0-4): B4 A2 91 4D

и ПК (VS Studio) правильно реконструирует его как 3,054199 * 10 ^ 8 (значение с плавающей запятой, которое, как я знаю, должно быть там)

Теперь я перемещаю этот eeprom для чтения с 8-битного Arduino, поэтому не уверен, что это связано с компилятором/платформой, но когда я пытаюсь прочитать 4 байта в 32-битное двойное слово, а затем преобразовать его в число с плавающей запятой, значение, которое я получаю, даже не близко.

Предполагая, что преобразование не может быть выполнено автоматически с помощью стандартного компилятора ansi-c, как можно вручную проанализировать 4 байта, чтобы они были числами с плавающей запятой?


person ben    schedule 11.10.2014    source источник
comment
Похоже на проблему порядка байтов, но без кода мы не можем знать наверняка. В моем ответе на этот вопрос я ссылаюсь на конвертер, чтобы вы могли попробовать перевернуть значения и посмотреть, сможете ли вы повторить результаты, которые вы видите, чтобы подтвердить.   -  person Shafik Yaghmour    schedule 11.10.2014
comment
Я согласен, что это, вероятно, endianness. Вы получаете около -3.0280572E-7 на Arduino?   -  person Patricia Shanahan    schedule 11.10.2014
comment
Интересно, что когда первый байт обрабатывается как MSB, преобразование компилятора (с плавающей запятой) дает: 3.03055283E9.. однако я помню, что изначально видел -3.02E-7, но не уверен, почему я больше не знаю. Когда первый байт обрабатывается как младший бит, компилятор приводит к: 1.30138995E9.   -  person ben    schedule 12.10.2014
comment
Вот как преобразовать массив байтов в float в PHP.   -  person Syed Azhar Abbas    schedule 27.06.2019


Ответы (3)


Самый безопасный способ, и из-за оптимизации компилятора также быстрый, как и любой другой, - использовать memcpy:

uint32_t dword = 0x4D91A2B4;
float f;
memcpy(&f, &dw, 4);

Демонстрация: http://ideone.com/riDfFw

person Ben Voigt    schedule 11.10.2014
comment
Спасибо, это сработало отлично! Поскольку пространство моей программы почти заполнено, я попытаюсь добавить пользовательскую реализацию memcpy, чтобы не включать stdlib.h... просто чтобы он не добавлял другой неиспользуемый код. - person ben; 12.10.2014
comment
@ben: я бы сначала просто скопировал прототип memcpy в ваш код ... большинство компиляторов распознают имя memcpy и фактически не будут вызывать функцию, когда аргумент размера фиксирован и мал. - person Ben Voigt; 12.10.2014

Как упомянул Шафик Ягмур в своем ответе, вероятно, это порядок байтов, так как это единственная логическая проблема, с которой вы можете столкнуться при такой низкоуровневой операции. В то время как Shafiks answer в вопросе, который он связал, в основном охватывает процесс решения такой проблемы, я просто оставлю вам некоторые Информация:

Как заявлено на форумах Anduino, Anduino использует Little Endian . Если вы не уверены в том, каким будет порядок следования байтов в системе, над которой вы в конечном итоге будете работать, но хотите сделать свой код полумультиплатформенным, вы можете проверить порядок следования байтов во время выполнения с помощью простого фрагмента кода:

bool isBigEndian(){
   int number = 1;
   return (*(char*)&number != 1);
}

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

Как это работает, так это то, что он проверяет первый байт int, хранящийся по адресу, указанному &number. Если первый байт не 1, это означает, что байты имеют формат Big Endian.

Кроме того, это будет работать только в том случае, если sizeof(int) > sizeof(char).

Вы также можете встроить это в свой код:

float getFromEeprom(int address){
   char bytes[sizeof(float)];
   if(isBigEndian()){
      for(int i=0;i<sizeof(float);i++)
         bytes[sizeof(float)-i] = EEPROM.read(address+i);
   }
   else{
      for(int i=0;i<sizeof(float);i++)
         bytes[i] = EEPROM.read(address+i);
   }
   float result;
   memcpy(&result, bytes, sizeof(float));
   return result;
}
person Paweł Stawarz    schedule 11.10.2014
comment
Спасибо, определенно конечность имела значение, но само приведение с плавающей запятой не давало правильного значения независимо от того, как я упорядочиваю байты. Я выбрал ответ Бена, так как он был первым, но мне очень нравится ваш код! - person ben; 12.10.2014

Вам нужно бросить на уровне указателя.

int     myFourBytes = /* something */;
float*  myFloat = (float*) &myFourBytes;
cout << *myFloat;

Должно сработать.

Если данные генерируются на другой платформе, которая хранит значения с обратным порядком байтов, вам потребуется вручную поменять местами байты. Например.:

unsigned char myFourBytes[4] = { 0xB4, 0xA2, 0x91, 0x4D };
std::swap(myFourBytes[0], myFourBytes[3]);
std::swap(myFourBytes[1], myFourBytes[2]);
person StilesCrisis    schedule 11.10.2014
comment
Спасибо, мне нравится эта красота! Кажется, используется встроенное преобразование компилятора без отдельного вызова... и он отлично работает! Единственная причина, по которой я бы не пошел с этим, - это строгое предупреждение о псевдонимах ... хотя я не уверен, что это значит, я поверю на слово людям, гораздо более опытным, чем я, в программировании, что это может вызвать неожиданные проблемы ! - person ben; 12.10.2014
comment
Это означает, что оптимизатору технически разрешено делать здесь ужасные вещи, поскольку строгое прочтение стандарта называет это поведение неопределенным. С практической точки зрения, в основных системах существует ГИГАНТСКОЕ количество существующего производственного кода, который сломался бы, если бы оптимизатор начал ошибаться. - person StilesCrisis; 12.10.2014
comment
Я думаю, что вы можете обойти проблему псевдонимов, используя объединение int и float вместо приведения двух типов. - person StilesCrisis; 12.10.2014