2011-02-09 5 views
5

Sto provando a scrivere una routine byteswap per un programma C++ in esecuzione su Win XP. Sto compilando con Visual Studio 2008. Questo è quello che è venuta in mente:Come effettuare il byteswap di un doppio?

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

E una funzione di prova:

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

Ottenere questi numeri non corrispondono:

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

Questo dopo aver letto:
How do I convert between big-endian and little-endian values in C++?
Little Endian - Big Endian Problem
Non è possibile utilizzare < < e >> il doppio, tra l'altro (se non mi sbaglio?)

+1

Potrebbe chiarire che cosa esattamente è stato inventato in Quake 2? Sicuramente non l'idea di avere un doppio e un int64 come campi della stessa unione? –

+0

Scusa .. Ho imparato C da Quake2 quindi mi piace fingere che sia il più grande: D Lo vedo (unione) dappertutto ora. – Darrell

risposta

4

Prova 3

Va bene, ha scoperto che c'è un modo migliore. L'altro modo in cui devi preoccuparti per l'ordine di impacchettare/disfare le cose. In questo modo non fare:

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

Invia:

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

Recv:

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

Anche se un double nella memoria principale è di 64 bit, su CPU x86 registri a doppia precisione sono 80 punte larghe. Quindi, se uno dei tuoi valori è memorizzato in un registro, ma l'altro effettua un round-trip attraverso la memoria principale e viene troncato a 64 bit, questo potrebbe spiegare le piccole differenze che stai vedendo.

Forse è possibile forzare le variabili a vivere nella memoria principale prendendo il loro indirizzo (e stampandolo, per impedire al compilatore di ottimizzarlo), ma non sono sicuro che funzioni.

+0

+1: Penso che sia quello che succede qui: 'a' è trattenuto in un registro e viene incrementato mentre 'c' è in memoria. – mmmmmmmm

3
b = byteswap(a); 

Questo è un problema. Dopo aver scambiato i byte, il valore non è più un doppio corretto. Memorizzarlo su un doppio causerà problemi quando la FPU normalizza il valore. Devi riporlo in un __int64 (lungo lungo). Modifica il tipo di ritorno del metodo.

+0

Questo ha davvero senso! Ok, controlleremo questo e torneremo da te – Darrell

0

Prova 2

D'accordo, ottenuto lavorando! Hans Passant aveva ragione. Mi hanno fatto pensare con il commento "non è più un doppio corretto". Quindi non puoi byteswap un float in un altro float perché potrebbe essere in un formato improprio, quindi devi passare bytes a un array di caratteri e sganciare indietro. Questo è il codice che ho usato:

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

E una semplice funzione di test:

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