2011-02-09 29 views
5

Estoy tratando de escribir una rutina byteswap para un programa C++ que se ejecuta en Win XP. Estoy compilando con Visual Studio 2008. Esto es lo que he llegado con:¿Cómo convertir bytes a un doble?

int byteswap(int v) // This is good 
{ 
    return _byteswap_ulong(v); 
} 

double byteswap(double v) // This doesn't work for some values 
{ 
    union { // This trick is first used in Quake2 source I believe :D 
     __int64 i; 
     double d; 
    } conv; 
    conv.d = v; 
    conv.i = _byteswap_uint64(conv.i); 
    return conv.d; 
} 

y una función a prueba:

void testit() { 
    double a, b, c; 
    CString str; 

    for (a = -100; a < 100; a += 0.01) { 
     b = byteswap(a); 
     c = byteswap(b); 
     if (a != c) { 
      str.Format("%15.15f %15.15f %15.15f", a, c, a - c); 
     } 
    } 
} 

Conseguir estos números no emparejan:

 
-76.789999999988126 -76.790000000017230 0.000000000029104 
-30.499999999987718 -30.499999999994994 0.000000000007276 
 41.790000000014508  41.790000000029060 -0.000000000014552 
 90.330000000023560  90.330000000052664 -0.000000000029104 

Esto es después de haber leído:
How do I convert between big-endian and little-endian values in C++?
Little Endian - Big Endian Problem
no puede utilizar < < y >> en el doble, por cierto (si no me equivoco?)

+1

Podría aclarar qué es exactamente fue inventado en Quake 2? Seguramente no la idea de tener un doble y un int64 como campos de la misma unión? –

+0

Lo siento ... aprendí C de Quake2 así que me gusta fingir que es el mejor: D Lo veo (unión) por todos lados ahora. – Darrell

Respuesta

4

Trate 3

bien, descubrió que hay una manera mejor. A la inversa, debes preocuparte por el orden en que empacas/desempacas cosas. De esta manera usted no:

// int and float 
static void swap4(void *v) 
{ 
    char in[4], out[4]; 
    memcpy(in, v, 4); 
    out[0] = in[3]; 
    out[1] = in[2]; 
    out[2] = in[1]; 
    out[3] = in[0]; 
    memcpy(v, out, 4); 
} 

// double 
static void swap8(void *v) 
{ 
    char in[8], out[8]; 
    memcpy(in, v, 8); 
    out[0] = in[7]; 
    out[1] = in[6]; 
    out[2] = in[5]; 
    out[3] = in[4]; 
    out[4] = in[3]; 
    out[5] = in[2]; 
    out[6] = in[1]; 
    out[7] = in[0]; 
    memcpy(v, out, 8); 
} 

typedef struct 
{ 
    int theint; 
    float thefloat; 
    double thedouble; 
} mystruct; 


static void swap_mystruct(void *buf) 
{ 
    mystruct *ps = (mystruct *) buf; 
    swap4(&ps->theint); 
    swap4(&ps->thefloat); 
    swap8(&ps->thedouble); 
}  

Enviar:

char buf[sizeof (mystruct)]; 
    memcpy(buf, &s, sizeof (mystruct)); 
    swap_mystruct(buf); 

Recv:

mystruct s; 
    swap_mystruct(buf); 
    memcpy(&s, buf, sizeof (mystruct)); 
6

Aunque un double en la memoria principal es de 64 bits, en las CPU x86 registros de doble precisión son 80 bits de ancho. Entonces, si uno de sus valores está almacenado en un registro, pero el otro realiza un viaje de ida y vuelta a través de la memoria principal y se trunca a 64 bits, esto podría explicar las pequeñas diferencias que está viendo.

Quizás puedas forzar a las variables a vivir en la memoria principal tomando su dirección (e imprimiéndola, para evitar que el compilador la optimice), pero no estoy seguro de que esto funcione.

+0

+1: Creo que eso es lo que sucede aquí: 'a' se mantiene en un registro y se incrementa allí mientras 'c' está en la memoria. – mmmmmmmm

3
b = byteswap(a); 

Eso es un problema. Después de intercambiar los bytes, el valor ya no es un doble correcto. Volver a almacenarlo en un doble va a causar problemas sutiles cuando la FPU normaliza el valor. Tienes que guardarlo en un __int64 (largo). Modificar el tipo de devolución del método.

+0

¡Eso realmente tiene sentido! De acuerdo, analizaré esto y te responderé – Darrell

0

Trate 2

bien, tengo trabajo! Hans Passant tenía razón. Me hicieron pensar con el comentario "ya no es un doble correcto". Por lo tanto, no puede convertir un flotador en un flotador en otro flotador porque puede estar en un formato incorrecto, por lo que debe realizar un cambio de bytes a una matriz char y volver a deshacer la marcha atrás. Este es el código que utilicé:

int pack(int value, char *buf) 
{ 
    union temp { 
     int value; 
     char c[4]; 
    } in, out; 
    in.value = value; 
    out.c[0] = in.c[3]; 
    out.c[1] = in.c[2]; 
    out.c[2] = in.c[1]; 
    out.c[3] = in.c[0]; 
    memcpy(buf, out.c, 4); 
    return 4; 
} 

int pack(float value, char *buf) 
{ 
    union temp { 
     float value; 
     char c[4]; 
    } in, out; 
    in.value = value; 
    out.c[0] = in.c[3]; 
    out.c[1] = in.c[2]; 
    out.c[2] = in.c[1]; 
    out.c[3] = in.c[0]; 
    memcpy(buf, out.c, 4); 
    return 4; 
} 

int pack(double value, char *buf) 
{ 
    union temp { 
     double value; 
     char c[8]; 
    } in, out; 
    in.value = value; 
    out.c[0] = in.c[7]; 
    out.c[1] = in.c[6]; 
    out.c[2] = in.c[5]; 
    out.c[3] = in.c[4]; 
    out.c[4] = in.c[3]; 
    out.c[5] = in.c[2]; 
    out.c[6] = in.c[1]; 
    out.c[7] = in.c[0]; 
    memcpy(buf, out.c, 8); 
    return 8; 
} 

int unpack(char *buf, int *value) 
{ 
    union temp { 
     int value; 
     char c[4]; 
    } in, out; 
    memcpy(in.c, buf, 4); 
    out.c[0] = in.c[3]; 
    out.c[1] = in.c[2]; 
    out.c[2] = in.c[1]; 
    out.c[3] = in.c[0]; 
    memcpy(value, &out.value, 4); 
    return 4; 
} 

int unpack(char *buf, float *value) 
{ 
    union temp { 
     float value; 
     char c[4]; 
    } in, out; 
    memcpy(in.c, buf, 4); 
    out.c[0] = in.c[3]; 
    out.c[1] = in.c[2]; 
    out.c[2] = in.c[1]; 
    out.c[3] = in.c[0]; 
    memcpy(value, &out.value, 4); 
    return 4; 
} 

int unpack(char *buf, double *value) 
{ 
    union temp { 
     double value; 
     char c[8]; 
    } in, out; 
    memcpy(in.c, buf, 8); 
    out.c[0] = in.c[7]; 
    out.c[1] = in.c[6]; 
    out.c[2] = in.c[5]; 
    out.c[3] = in.c[4]; 
    out.c[4] = in.c[3]; 
    out.c[5] = in.c[2]; 
    out.c[6] = in.c[1]; 
    out.c[7] = in.c[0]; 
    memcpy(value, &out.value, 8); 
    return 8; 
} 

y una función de prueba sencilla:

typedef struct 
{ 
    int theint; 
    float thefloat; 
    double thedouble; 
} mystruct; 

void PackStruct() 
{ 
    char buf[sizeof (mystruct)]; 
    char *p; 
    p = buf; 

    mystruct foo, foo2; 
    foo.theint = 1; 
    foo.thefloat = 3.14f; 
    foo.thedouble = 400.5; 

    p += pack(foo.theint, p); 
    p += pack(foo.thefloat, p); 
    p += pack(foo.thedouble, p); 

    // Send or recv char array 

    p = buf; 
    p += unpack(p, &foo2.theint); 
    p += unpack(p, &foo2.thefloat); 
    p += unpack(p, &foo2.thedouble); 
}