2010-08-05 13 views
12

¿Cómo puedo serializar dobles y flotantes en C?Serializar doble y flotar con C

Tengo el siguiente código para serializar cortos, ints y caracteres.

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:

me encontré con estas funciones desde this question

Edición 2:

El propósito de serialización es el envío de datos a un socket UDP y garantizar que se puede deserializar el la otra máquina, incluso si la endianidad es diferente. ¿Hay otras "mejores prácticas" para realizar esta funcionalidad dado que tengo que serializar ints, dobles, flotantes y char *?

+2

Esto parece no tener sentido - se termina con un tampón que contiene el número que es del mismo tamaño que el número. ¿Qué crees que logran estas funciones? ¿Por qué no usar memcpy(), por ejemplo? –

+6

@Neil Butterworth Sus funciones son independientes del anfitrión endian. – nos

+0

@Neil Butterworth sí que son, el código determina que el byte más significativo está escrito en la dirección más baja, no el orden de bits de host –

Respuesta

4

Después de su actualización, menciona que los datos se transmitirán mediante UDP y solicite las mejores prácticas. Recomiendo encarecidamente enviar los datos como texto, quizás incluso con un añadido de som (XML). La depuración de errores endian-releated a través de una línea de transmisión es una pérdida de everybodys tiempo

Sólo mi 2 centavos en las "mejores prácticas" parte de su pregunta

+3

Aunque el envío de texto plano estaría bien, uno de los requisitos es usar la menor cantidad de ancho de banda posible. – Trevor

+1

@Trevor Enviar texto no significa necesariamente mucho ancho de banda extra. Por ejemplo, enviar el número entero 1 toma 4 bytes (en su plataforma) cuando se envía como un int y 2 (suponiendo un separador) cuando se envía como texto. así que esto tiende a emparejarse. y el texto es mucho, mucho más simple de manejar y depurar. –

+0

Okidoki, a continuación, utilizar el ejemplo de código he mostrado en mi respuesta anterior, y quiero saber si funciona para ti –

13

Recuerdo haber visto el molde utilizado en mi ejemplo a continuación en el buen código fuente de Quake de la rutina "rsqrt", que contenía el comentario más genial que había visto en ese momento (Google it, te gustará)

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

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

+2

me gustaría añadir 'assumes_sz_float_eq_sz_int char [(2 * (int) (sizeof (int) == sizeof (float))) - 1];' en la parte superior de la función. –

+0

@David X ¿Como un cheque en tiempo de compilación? Buena idea, suelo hacer ese truco con enumeraciones, pero supongo que una longitud de matriz negativa funciona igual de bien –

+0

@SCMadden, que sería 'enum {foo = 0, bar = (condición)};', ¿verdad? Eso en realidad podría ser mejor, aunque obtienes un contaminante de espacio de nombres extra con 'foo'. (Además, el lanzamiento a 'int' de arriba es inútil, creo que pensé que la comparación devolvía un' size_t' por alguna razón). –

1

Siempre puede utilizar los sindicatos 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 robusto que simplemente echando la dirección de la double a un char*, pero al menos mediante el uso de sizeof() durante todo el código que está evitando problemas cuando un tipo de datos ocupa más/menos bytes de lo que pensaba que did (esto no ayuda si está moviendo datos entre platfo rms que usan diferentes tamaños para double).

Para flotadores, 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.

2

Para la pregunta estrecha sobre float, tenga en cuenta que probablemente termine asumiendo que ambos extremos del cable están utilizando la misma representación para punto flotante. Esto podría estar seguro hoy dado el uso generalizado de IEEE-754, pero tenga en cuenta que algunos DSP actuales (creo que blackfins) 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 una orden de bytes en el cable, y un código modificado en cada extremo. O bien, el lanzamiento de puntero tipo punteado o la unión funcionarán en la práctica. Ambos invocan el comportamiento definido por la implementación, pero siempre y cuando verifique y pruebe eso no es gran cosa.

Dicho esto, el texto suele ser tu amigo para transferir puntos flotantes entre plataformas.El truco es no usar muchos más caracteres que realmente se necesitan para convertirlo nuevamente.

Con todo, lo recomiendo dar una seria consideración a la utilización de una biblioteca como XDR que es robusto, estado alrededor por un tiempo, y se ha borrado en contra de todos los casos de esquina y bordes afilados.

Si insiste en rodar su propia, tener cuidado acerca de cuestiones sutiles como si int es de 16 bits, 32 bits, 64 bits o incluso, además de la representación de float y double.

+0

AFAIK el blackfin no tiene FPU. –

+0

@ S.C, tal vez estoy recordando un TI DSP entonces. Toda la IEEE-754 aún sería más costosa de lo que le gustaría implementar en un DSP, después de todo. – RBerteig

+0

El texto para números realmente significa pedir sobrecarga masiva ... – yeoman

1

Para empezar, nunca debe suponer que short, int etc. tienen el mismo ancho en ambos lados. Sería mucho mejor utilizar los tipos uint32_t, etc. (sin firmar) que tienen ancho conocido en ambos lados.

Luego de confirmar que usted no tiene problemas con endianess existen las macros/funciones ntohhtos etc., que suelen ser mucho más eficiente que cualquier otra cosa que puede hacer por su cuenta. (en el hardware de Intel son, por ejemplo, solo una instrucción de ensamblador). Por lo tanto, no tiene que escribir funciones de conversión, básicamente ya están allí, simplemente envíe su puntero buffer a un puntero del tipo de entero correcto.

Para float es probable que suponga que son de 32 bits y tienen la misma representación en ambos lados. Así que creo que una buena estrategia sería usar un lanzamiento de puntero a uint32_t* y luego la misma estrategia que la anterior.

Si crees que podrías tener representaciones diferentes de float, tendrías que dividir en mantisa y exponente. Probablemente podría usar frexpf para eso.

7

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

El par de números representa 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; 
} 
11

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

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

El modo binary-file-haters: serialice todo como texto, flotadores incluidos. Utilice el especificador de formato de impresión "%a" 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 leerlos de nuevo con strtod o cualquiera de la familia de funciones scanf.

+0

'% a' no está en C89, pero está en C99. Notablemente, C99 también maneja mejor NaN y infinitos al especificar cómo 'printf' los formatea y' scanf' los lee. – RBerteig

+0

Buen punto. Si necesita compatibilidad con C89, simplemente escriba su propio código 'printf ("% a ", f)'. Solo se necesitan unas 20 líneas si no necesita soporte para argumentos no finitos, y 10-15 más si lo hace. A diferencia de la impresión de números flotantes en decimales, imprimirlos en hexadecimal es muy fácil y la implementación trivial hace lo que esperas (es decir, realmente funciona). –

+0

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

4

Puede serializar de forma portátil en el estándar IEEE-754, independientemente de la representación nativa: (. es decir, el caso común)

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 las máquinas que utilizan IEEE-754, todo lo que necesita hacer para conseguir el número es fread().De lo contrario, decodifique los bytes usted mismo (sign * 2^(exponent-127) * 1.mantissa).

Nota: cuando se serializa en sistemas en los que el doble nativa es más preciso que el IEEE doble, puede encontrarse con errores off-by-one en el bit bajo.

Espero que esto ayude.

+0

Todavía usamos declaraciones goto? – thegreendroid

+5

Cuando sea apropiado. –

0

Se puede utilizar una biblioteca para evitar la pérdida de tiempo.

Aquí hay un ejemplo usando la biblioteca 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);