Преобразование float в 4 uint8_t

У меня есть переменная типа float которую мне нужно отправить по протоколу CAN. Для этого 32-разрядное число с плавающей запятой должно быть разрезано на 4 переменные uint8_t.

Я понятия не имею, как это сделать. Сначала я думал о преобразовании числа с плавающей точкой в int, но некоторые ответы, которые я нашел в Интернете, которые используют приведение или объединение, похоже, не работают.

Вот простой пример того, что я пытаюсь сделать:

float f;
uint8_t ut1,ut2,ut3,ut4;

//8 first bits of f into ut1
//8 second bits of f in ut2
...

// Then I can send the uint8_t through CAN
...

Ответ 1

Обычно вы делаете это, бросая float в массив uint8_t.

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

uint8_t *array;
array = (unit8_t*)(&f);

в С++ используйте reinterpret_cast

uint8_t *array;
array = reinterpret_cast<uint8_t*>(&f);

Затем массив [0],..., array [3] - ваши байты.

Ответ 2

Сначала вы должны заметить, что стандарт не налагает никаких ограничений по размеру на float. Возможно, что float не будет вписываться в четыре байта на какой-либо воображаемой архитектуре (хотя я не знаю никого). Вы должны хотя бы (static_) утверждать, что он подойдет, прежде чем что-либо предпринять.

Тогда я думаю, что самый простой способ - утверждать, что CHAR_BIT есть 8, и использовать юридическое сглаживание в unsigned char* с помощью reinterpret_cast:

static_assert(sizeof(float) == 4);
float f = 0; // whatever value
unsigned char* float_as_char = reinterpret_cast<unsigned char*>(&f);

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

static_assert(sizeof(float) == 4);
float f = 0; // whatever value
uint8_t bytes[4];
std::memcpy(bytes, &f);
// Fix up the order of the bytes in "bytes" now.

Ответ 3

Здесь применяется подход объединения, который дает отдельные имена для целых частей вместо одного массива:

union {
    float f;
    struct {
        uint8_t ut1, ut2, ut3, ut4;
    } bytes;
} value;
value.f = 1.f;
uint8_t first = value.bytes.ut1;

Я изначально был обеспокоен тем, что это использование union не является строго законным в соответствии со стандартом: С++ Undefined поведение с объединениями, но аргумент ComicSansMS в комментарии ответ на rashmatash является убедительным.

Ответ 4

Вы можете выполнить эту незаконную операцию:

float f = someFloatValue;
uint8_t* i = reinterpret_cast<uint8_t*>(&f);

Хотя это работает большую часть времени, оно не поддерживается стандартом С++, и компиляторы могут генерировать код с поведением undefined.

Другим решением является использование объединений:

union{
    float f;
    uint8_t i[4];
}
f = someFloatValue;
// now i contain the bit pattern of f

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

Вы также можете упаковать значение f в 32-битное целое число. Это, однако, может привести к потере некоторой точности, но в зависимости от того, насколько точно вы хотите сохранить f, было бы лучшим решением.