Преобразование из float в char array и обратно в C / C++

При разработке программного обеспечения для микроконтроллеров довольно часто приходится преобразовывать данные из типа float в массив из четырех байт и обратно. Если использовать для этого тип union, то такая задача решается элементарно.

Тип float в языках C и C++

Начнем с типа данных float в языках C и C++. Этот тип используется для хранения вещественных чисел с плавающей точкой в формате IEEE 754. Диапазон возможных значений: [1,2E-38; 3,4E+38], точность – шесть знаков. Если мы оперируем целым числом в формате int, long и т.п., запаковать его в нужное количество байт не составит труда, для этого даже не нужно использовать специальные средства языка. Ниже представлен пример на C++.

#include <iostream>
using namespace std;

int main()
{
    long val = 89564732449;
    unsigned char arr[4] = { 0, 0, 0, 0 };
    
    arr[0] = val & 0xff;
    arr[1] = (val >> 8) & 0xff;
    arr[2] = (val >> 16) & 0xff;
    arr[3] = (val >> 24) & 0xff;

    cout << std::hex 
         << (int)arr[0] << " " << (int)arr[1] << " "
         << (int)arr[2] << " " << (int)arr[3] << endl;
    
    return 0;
}

В результате появится следующая строка:

21 5c 79 da

Это исходные целое число, разложенное по байтам и представленное в шестнадцатеричном формате. Для начального знакомства с форматом IEEE 754 предлагаю прочитать вот эту статьюФормат числа выглядит так.

IEEE753

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

Тип union в языках C и C++

Тип union – это специальный тип данных, позволяющий хранить переменные различных типов данных в одной и той же области памяти. В C и C++ синтаксис немного отличается. В C++ использование union выглядит так.

#include <iostream>
using namespace std;

int main()
{
    union DataType
    {
        unsigned long       longVal;
        unsigned short  shortVal[2];
    };

    DataType sample;
    sample.longVal = 45265795;
    cout << "long value: " << sample.longVal << endl;
    cout << "two short values: " << sample.shortVal[0] << " " << sample.shortVal[1] << endl;

    return 0;
}

Получим следующий результат.

long value: 2b2b383
two short values: b383 2b2

Реализуем этот же функционал на языке C.

#include <stdio.h>

int main()
{
    union DataType
    {
        unsigned long       longVal;
        unsigned short  shortVal[2];
    };

    union DataType sample;
    sample.longVal = 45265795;
    printf("long value: %x\n", sample.longVal);
    printf("two short values: %x %x\n", sample.shortVal[0], sample.shortVal[1]);

 return 0;
}

Как видно из представленных примеров: одна и та же область памяти (четыре байта) представляется с одной стороны как число типа unsigned long, с другой как два числа типа unsigned short.

Преобразование из float в char[4] и обратно в C и C++

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

Реализуем это на языке C++.

#include <iostream>
using namespace std;

int main()
{
    union DataType
    {
        float fVal;
        unsigned char  buf[4];
    };

    DataType sample;
    sample.fVal = 123.98;
    cout << "float value: " << sample.fVal << endl;
    cout << std::hex << "buffer: "
        << (int)sample.buf[0] << " " << (int)sample.buf[1] << " "
        << (int)sample.buf[2] << " " << (int)sample.buf[3] << endl;

    system("pause");
    return 0;
}

Массив sample.buf можно передавать по сети (например: Modbus), а также принимать в него входные данные, содержащие значение типа float.  Конечно, можно и вообще без union обойтись и сделать следующим образом.

float fVal = 123.98;
cout << std::hex << "buffer: "
     << ((int)(*((char*)&fVal)) & 0xff) << " " << ((int)(*((char*)&fVal + 1)) & 0xff) << " "
     << ((int)(*((char*)&fVal + 2)) & 0xff) << " " << ((int)(*((char*)&fVal + 3)) & 0xff) << endl;

Но это менее интуитивно)).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *