
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*?
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.

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?
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.

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
.

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

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.
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