Serializar doble y flotar con C

11 minutos de lectura

avatar de usuario
Trevor

¿Cómo puedo serializar dobles y flotantes en C?

Tengo el siguiente código para serializar shorts, ints y chars.

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

Editar:

Encontré estas funciones de esta pregunta.

Edición 2:

El propósito de la serialización es enviar datos a un socket UDP y garantizar que se puedan deserializar en la otra máquina incluso si el endianness es diferente. ¿Existen otras “mejores prácticas” para realizar esta funcionalidad dado que tengo que serializar ints, doubles, floats y char*?

  • Esto parece inútil: termina con un búfer que contiene el número que tiene el mismo tamaño que el número. ¿Qué crees que logran estas funciones? ¿Por qué no usar memcpy(), por ejemplo?

    luego

    5 de agosto de 2010 a las 19:22


  • @Neil Butterworth Sus funciones son independientes del host endian.

    – nos

    5 de agosto de 2010 a las 19:27


  • @Neil Butterworth Sí lo son, el CÓDIGO determina que el byte más significativo se escribe en la dirección más baja, no en el host endianness

    – SC Madsen

    5 de agosto de 2010 a las 19:30


  • Bueno, intentan serlo, pero se supone que las entradas son int32_t, etc. y se debe tener más cuidado con respecto a los cambios a la derecha de las entradas firmadas.

    – nos

    5 de agosto de 2010 a las 19:33


  • Déle crédito por darse cuenta de que necesita hacer esto en absoluto… He visto muchos “formatos de archivo” definidos sin preocuparse por el orden de los bytes o el tamaño de (int) para contar. Dado que está asignando solo el byte mínimo del resultado desplazado a la derecha a un carácter sin firmar, todo debería funcionar. esta jodido si sizeof(int)==2 en alguna plataforma sin embargo.

    – R. Berteig

    5 de agosto de 2010 a las 20:48

La forma portátil: uso frexp serializar (convertir a entero mantisa y exponente) y ldexp para deserializar.

La forma simple: suponga que en 2010 cualquier máquina que le interese use IEEE float, declare una unión con un float elemento y un uint32_t elemento, y use su código de serialización entero para serializar el flotante.

A la manera de los que odian los archivos binarios: serialice todo como texto, flotantes incluidos. Utilizar el "%a" especificador de formato printf para obtener un flotante hexadecimal, que siempre se expresa exactamente (siempre que no limite la precisión con algo como "%.4a") y no está sujeto a errores de redondeo. Puede volver a leerlos con strtod o cualquiera de los scanf familia de funciones.

  • %a no está en C89, pero está en C99. En particular, C99 también maneja mejor NaN e infinitos al especificar cómo printf les da formato y scanf los lee

    – R. Berteig

    10 de agosto de 2010 a las 21:22

  • Buen punto. Si necesita compatibilidad con C89, simplemente escriba la suya printf("%a", f) código. Solo toma alrededor de 20 líneas si no necesita soporte para argumentos no finitos, y 10-15 más si lo necesita. A diferencia de imprimir números de coma flotante en decimal, imprimirlos en hexadecimal es muy fácil y la implementación trivial hace lo que espera (es decir, realmente funciona).

    – R.. GitHub DEJA DE AYUDAR A ICE

    11 de agosto de 2010 a las 4:52

  • frexp devuelve el exponente entero, pero ¿cómo se obtiene la mantisa como un número entero?

    – jw013

    5 sep 2012 a las 20:20

  • frexp devuelve la mantisa en el rango [1,2). So just scale by 2^23 or 2^52 and then cast to the appropriate integer type.

    – R.. GitHub STOP HELPING ICE

    Sep 5, 2012 at 22:08

  • @R.. Actually, I believe it’s in the range [0.5,1), is it not?

    – Alexis King

    Jul 30, 2013 at 6:38

user avatar
S.C. Madsen

I remember first seeing the cast used in my example below in the good old Quake source code of the “rsqrt” routine, containing the coolest comment I’d seen at the time (Google it, you’ll like it)

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

Espero haber entendido su pregunta (y el código de ejemplo) correctamente. Déjame saber si esto fue útil?

  • yo podria agregar char assumes_sz_float_eq_sz_int[(2*(int)(sizeof(int)==sizeof(float)))-1]; en la parte superior de la función.

    – David X.

    5 de agosto de 2010 a las 23:37

  • @David X ¿Como una verificación en tiempo de compilación? Buena idea, normalmente hago ese truco con enumeraciones, pero supongo que una longitud de matriz negativa funciona igual de bien

    – SC Madsen

    6 de agosto de 2010 a las 4:39

  • Para aquellos de ustedes que buscan el comentario, vean esta enlace de wikipedia

    – el androide verde

    10 de enero de 2013 a las 2:27

  • Este es un ejemplo de “tipo de juego de palabras” y no es muy seguro. Muchos ejemplos en SO, por ejemplo, stackoverflow.com/questions/222266/…

    – Cuadue

    28 de julio de 2014 a las 18:27

  • “pero nunca encontré un compilador” Las optimizaciones basadas en alias tienen la mala costumbre de generar código que aparentemente funciona. más del tiempo. El problema es que no hay garantías. “a la crítica aún no le ha seguido una (en mi opinión) mejor solución”. Escriba juegos de palabras a través de la unión, o copie con memcpy está bien definido dentro del estándar.

    – usuario694733

    19 de octubre de 2018 a las 7:14


Esto empaqueta un valor de punto flotante en un int y long long pair, que luego puede serializar con sus otras funciones. los unpack() La función se utiliza para deserializar.

El par de números representan el exponente y la parte fraccionaria del número respectivamente.

#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;
}

Puede serializar de forma portátil en IEEE-754 independientemente de la representación nativa:

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

En máquinas que utilizan IEEE-754 (es decir. el caso comun), todo lo que necesita hacer para obtener el número es un fread(). De lo contrario, decodifique los bytes usted mismo (sign * 2^(exponent-127) * 1.mantissa).

Nota: al serializar en sistemas donde el doble nativo es más preciso que el doble IEEE, es posible que encuentre errores de uno en uno en el bit bajo.

Espero que esto ayude.

avatar de usuario
rberteig

Para la pregunta estrecha sobre float, tenga en cuenta que probablemente termine asumiendo que ambos extremos del cable usan la misma representación para el punto flotante. Esto podría ser seguro hoy en día dado el uso generalizado de IEEE-754, pero tenga en cuenta que algunos DSP actuales (creo que los aletas negras) usan una representación diferente. En los viejos tiempos, había al menos tantas representaciones de coma flotante como fabricantes de hardware y bibliotecas, por lo que este era un problema mayor.

Incluso con la misma representación, es posible que no se almacene con el mismo orden de bytes. Eso requerirá decidir sobre un orden de bytes en el cable y un código modificado en cada extremo. En la práctica, funcionarán tanto el molde de puntero con juego de palabras como la unión. Ambos están invocando el comportamiento definido por la implementación, pero siempre que verifique y pruebe eso no es un gran problema.

Dicho esto, el texto suele ser su amigo para transferir puntos flotantes entre plataformas. El truco es no usar demasiados caracteres más de los que realmente se necesitan para volver a convertirlo.

Con todo, recomendaría considerar seriamente el uso de una biblioteca como XDR que es robusto, ha existido por un tiempo y se ha frotado contra todas las esquinas afiladas y los bordes de las cajas.

Si insiste en rodar el suyo, tenga cuidado con cuestiones sutiles como si int es de 16 bits, 32 bits o incluso 64 bits además de la representación de float y double.

  • AFAIK, el aleta negra no tiene FPU.

    – SC Madsen

    5 de agosto de 2010 a las 21:08

  • @SC, tal vez estoy recordando un TI DSP entonces. Todo IEEE-754 aún sería más costoso de lo que le gustaría implementar en un DSP, después de todo.

    – R. Berteig

    5 de agosto de 2010 a las 21:30

avatar de usuario
jonathan leffler

Después de su actualización, menciona que los datos se transmitirán mediante UDP y solicita las mejores prácticas. Recomiendo encarecidamente enviar los datos como texto, tal vez incluso con algún marcado agregado (XML). La depuración de errores relacionados con endian en una línea de transmisión es una pérdida de tiempo para todos.

Solo mis 2 centavos en la parte de “mejores prácticas” de su pregunta

  • AFAIK, el aleta negra no tiene FPU.

    – SC Madsen

    5 de agosto de 2010 a las 21:08

  • @SC, tal vez estoy recordando un TI DSP entonces. Todo IEEE-754 aún sería más costoso de lo que le gustaría implementar en un DSP, después de todo.

    – R. Berteig

    5 de agosto de 2010 a las 21:30

avatar de usuario
bta

Siempre puedes usar uniones para serializar:

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];
}

Esto no es realmente más sólido que simplemente emitir la dirección del double a un char*pero al menos usando sizeof() a lo largo del código, está evitando problemas cuando un tipo de datos ocupa más/menos bytes de lo que pensaba (esto no ayuda si está moviendo datos entre plataformas que usan diferentes tamaños para double).

Para flotantes, simplemente reemplace todas las instancias de double con float. Es posible que pueda crear una macro astuta para generar automáticamente una serie de estas funciones, una para cada tipo de datos que le interese.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad