Подтвердить что ты не робот

Сериализовать double и float с C

Как я могу сериализировать двойные и плавающие в C?

У меня есть следующий код для сериализации шорт, ints и символов.

unsigned char * serialize_char(unsigned char *buffer, char value)
{
    buffer[0] = value;
    return buffer + 1;
}

unsigned char * serialize_int(unsigned char *buffer, int value)
{
    buffer[0] = value >> 24;
    buffer[1] = value >> 16;
    buffer[2] = value >> 8;
    buffer[3] = value;
    return buffer + 4;
}

unsigned char * serialize_short(unsigned char *buffer, short value)
{
    buffer[0] = value >> 8;
    buffer[1] = value;
    return buffer + 2;
}

Edit:

Я нашел эти функции из этого вопроса

Изменить 2:

Целью сериализации является передача данных в сокет UDP и гарантирование того, что его можно десериализовать на другом компьютере, даже если он отличается от другого. Существуют ли другие "лучшие практики" для выполнения этой функциональности, учитывая, что мне приходится сериализовать int, double, floats и char *?

4b9b3361

Ответ 1

После вашего обновления вы упомянули, что данные должны передаваться с использованием UDP и запрашивать лучшие практики. Я бы настоятельно рекомендовал отправлять данные в виде текста, возможно, даже с добавлением som markup (XML). Отладка ошибок, выведенных с первого конца по всей линии передачи, является пустой тратой времени на каждый день.

Только мои 2 цента на часть "лучших практик" вашего вопроса

Ответ 2

Я помню, как сначала увидел приведение, используемое в моем примере ниже, в добром старом исходном коде Quake подпрограммы "rsqrt", содержащем самый крутой комментарий, который я видел в то время (Google это вам понравится)

unsigned char * serialize_float(unsigned char *buffer, float value) 
{ 
    unsigned int ivalue = *((unsigned int*)&value); // warning assumes 32-bit "unsigned int"
    buffer[0] = ivalue >> 24;  
    buffer[1] = ivalue >> 16;  
    buffer[2] = ivalue >> 8;  
    buffer[3] = ivalue;  
    return buffer + 4; 
} 

Надеюсь, я правильно понял ваш вопрос (и примерный код). Дайте мне знать, было ли это полезно?

Ответ 3

Портативный способ: используйте frexp для сериализации (преобразование в целочисленную мантиссу и экспоненту) и ldexp для десериализации.

Простой способ: предположим, что в 2010 году любая машина, о которой вы заботитесь, использует IEEE float, объявляет объединение с элементом float и элементом uint32_t и использует код целочисленной сериализации для сериализации float.

Метод двоичных файлов-ненавистников: сериализуйте все как текст, включая плавающие. Используйте спецификатор формата "%a" printf, чтобы получить шестнадцатеричный float, который всегда точно выражается (если вы не ограничиваете точность чем-то вроде "%.4a") и не подвергаетесь ошибкам округления. Вы можете прочитать их обратно с помощью strtod или любого из семейств функций scanf.

Ответ 4

Это упаковывает значение с плавающей запятой в пару int и long long, которую вы затем можете сериализовать с другими функциями. Функция unpack() используется для десериализации.

Пара чисел представляет собой показатель экспоненты и дробную часть числа соответственно.

#define FRAC_MAX 9223372036854775807LL /* 2**63 - 1 */

struct dbl_packed
{
    int exp;
    long long frac;
};

void pack(double x, struct dbl_packed *r)
{
    double xf = fabs(frexp(x, &r->exp)) - 0.5;

    if (xf < 0.0)
    {
        r->frac = 0;
        return;
    }

    r->frac = 1 + (long long)(xf * 2.0 * (FRAC_MAX - 1));

    if (x < 0.0)
        r->frac = -r->frac;
}

double unpack(const struct dbl_packed *p)
{
    double xf, x;

    if (p->frac == 0)
        return 0.0;

    xf = ((double)(llabs(p->frac) - 1) / (FRAC_MAX - 1)) / 2.0;

    x = ldexp(xf + 0.5, p->exp);

    if (p->frac < 0)
        x = -x;

    return x;
}

Ответ 5

Вы можете переносить сериализацию в IEEE-754 независимо от собственного представления:

int fwriteieee754(double x, FILE * fp, int bigendian)
{
    int                     shift;
    unsigned long           sign, exp, hibits, hilong, lowlong;
    double                  fnorm, significand;
    int                     expbits = 11;
    int                     significandbits = 52;

    /* zero (can't handle signed zero) */
    if(x == 0) {
        hilong = 0;
        lowlong = 0;
        goto writedata;
    }
    /* infinity */
    if(x > DBL_MAX) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        lowlong = 0;
        goto writedata;
    }
    /* -infinity */
    if(x < -DBL_MAX) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        hilong |= (1 << 31);
        lowlong = 0;
        goto writedata;
    }
    /* NaN - dodgy because many compilers optimise out this test
     * isnan() is C99, POSIX.1 only, use it if you will.
     */
    if(x != x) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        lowlong = 1234;
        goto writedata;
    }

    /* get the sign */
    if(x < 0) {
        sign = 1;
        fnorm = -x;
    } else {
        sign = 0;
        fnorm = x;
    }

    /* get the normalized form of f and track the exponent */
    shift = 0;
    while(fnorm >= 2.0) {
        fnorm /= 2.0;
        shift++;
    }
    while(fnorm < 1.0) {
        fnorm *= 2.0;
        shift--;
    }

    /* check for denormalized numbers */
    if(shift < -1022) {
        while(shift < -1022) {
            fnorm /= 2.0;
            shift++;
        }
        shift = -1023;
    } else {
        /* take the significant bit off mantissa */
        fnorm = fnorm - 1.0;
    }
    /* calculate the integer form of the significand */
    /* hold it in a  double for now */

    significand = fnorm * ((1LL << significandbits) + 0.5f);

    /* get the biased exponent */
    exp = shift + ((1 << (expbits - 1)) - 1);   /* shift + bias */

    /* put the data into two longs */
    hibits = (long)(significand / 4294967296);  /* 0x100000000 */
    hilong = (sign << 31) | (exp << (31 - expbits)) | hibits;
    lowlong = (unsigned long)(significand - hibits * 4294967296);

 writedata:
    /* write the bytes out to the stream */
    if(bigendian) {
        fputc((hilong >> 24) & 0xFF, fp);
        fputc((hilong >> 16) & 0xFF, fp);
        fputc((hilong >> 8) & 0xFF, fp);
        fputc(hilong & 0xFF, fp);

        fputc((lowlong >> 24) & 0xFF, fp);
        fputc((lowlong >> 16) & 0xFF, fp);
        fputc((lowlong >> 8) & 0xFF, fp);
        fputc(lowlong & 0xFF, fp);
    } else {
        fputc(lowlong & 0xFF, fp);
        fputc((lowlong >> 8) & 0xFF, fp);
        fputc((lowlong >> 16) & 0xFF, fp);
        fputc((lowlong >> 24) & 0xFF, fp);

        fputc(hilong & 0xFF, fp);
        fputc((hilong >> 8) & 0xFF, fp);
        fputc((hilong >> 16) & 0xFF, fp);
        fputc((hilong >> 24) & 0xFF, fp);
    }
    return ferror(fp);
}

В машинах с использованием IEEE-754 (т.е. в общем случае) все, что вам нужно сделать, чтобы получить номер, это fread(). В противном случае декодируйте байты самостоятельно (sign * 2^(exponent-127) * 1.mantissa).

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

Надеюсь, что это поможет.

Ответ 6

Для узкого вопроса о float обратите внимание, что вы, вероятно, в конечном итоге предполагаете, что оба конца провода используют одно и то же представление для с плавающей запятой. Сегодня это может быть безопасно с учетом широко распространенного использования IEEE-754, но обратите внимание, что некоторые современные DSP (я считаю, blackfins) используют другое представление. В прежние времена было, по крайней мере, столько представлений о плавающей запятой, сколько было производство аппаратных средств и библиотек, поэтому это была большая проблема.

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

Тем не менее, текст часто является вашим другом для переноса плавающей запятой между платформами. Хитрость заключается в том, чтобы не использовать слишком много символов, которые действительно необходимы для его преобразования.

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

Если вы настаиваете на сворачивании своих собственных, позаботьтесь о тонких проблемах, таких как: int - 16 бит, 32 бита или даже 64 бита в дополнение к представлению float и double.

Ответ 7

Вы можете всегда использовать союзы для сериализации:

void serialize_double (unsigned char* buffer, double x) {
    int i;
    union {
        double         d;
        unsigned char  bytes[sizeof(double)];
    } u;

    u.d = x;
    for (i=0; i<sizeof(double); ++i)
        buffer[i] = u.bytes[i];
}

На самом деле это не является более надежным, чем просто отбрасывание адреса double до char*, но, по крайней мере, с помощью sizeof() на протяжении всего кода вы избегаете проблем, когда тип данных занимает больше/меньше, чем вы думали (это не помогает, если вы перемещаете данные между платформами, использующими разные размеры для double).

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

Ответ 8

Чтобы начать, вы никогда не должны предполагать, что short, int и т.д. имеют одинаковую ширину с обеих сторон. Было бы гораздо лучше использовать типы uint32_t и т.д. (Без знака), которые имеют известную ширину с обеих сторон.

Затем, чтобы быть уверенным, что у вас нет проблем с endianess, есть макросы/функции ntoh htos и т.д., которые обычно намного эффективнее всего, что вы можете сделать по своему усмотрению. (на оборудовании Intel это, например, только одна инструкция ассемблера.) Таким образом, вам не нужно писать функции преобразования, в основном они уже существуют, просто нарисуйте указатель buffer на указатель правильного целочисленного типа.

Для float вы, вероятно, предположите, что они 32 бит и имеют одинаковое представление с обеих сторон. Поэтому я считаю, что хорошей стратегией было бы использование указателя на uint32_t*, а затем та же стратегия, что и выше.

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

Ответ 9

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

Вот пример, используя библиотеку Binn:

  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "John");
  binn_object_set_double(obj, "total", 2.55);

  // send over the network
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);