2012-05-16 12 views
10

Ho lavorato di recente a un sistema che ha bisogno di memorizzare e caricare grandi quantità di dati, compresi valori a virgola mobile a precisione singola. Ho deciso di standardizzare su ordine di byte di rete per gli interi, e anche deciso di memorizzare valori in virgola mobile in big-endian formato, vale a dire:Serializzazione portatile dei valori a virgola mobile IEEE754

|-- Byte 0 --| |-- Byte 1 -| Byte 2 Byte 3 
    #  ####### #  ####### ######## ######## 
Sign  Exponent   Mantissa 
1b 8b, MSB first 23b, MSB first 

Idealmente, voglio fornire funzioni come htonl() e ntohl(), dal momento che ho già state usando questi per fare lo swabing di interi, e voglio anche implementarlo in un modo che abbia il massimo di indipendenza dalla piattaforma possibile (pur supponendo che il tipo float corrisponda ai valori in virgola mobile a 32 bit IEEE754). C'è un modo, forse usando ieee754.h, per fare questo?

Ho una risposta che sembra al lavoro, e lo posterò sotto, ma sembra piuttosto lento e inefficiente e gradirei eventuali suggerimenti su come renderlo più veloce e/o più affidabile.

+0

Che dire di questo: http://stackoverflow.com/a/2782742/1327576? – smocking

+0

Ho guardato quella risposta, e chiaramente dipende dal presupposto che la rappresentazione dell'host sia little-endian. Sto cercando qualcosa che sia agnostico su host-byte-order. –

+0

Probabilmente 'snprintf (b, sizeof (b),"% .9001f ", yourvalue)' (rappresentazione basata su testo) è la più portabile. –

risposta

6

Molto più semplice, e in base al presupposto stesso come il vostro (che è che i tipi float e interi hanno lo stesso ordine dei byte, ed è quasi universalmente valida - realisticamente non incontrerete mai un sistema in cui non è vero):

#include <string.h> 

float htonf(float val) { 
    uint32_t rep; 
    memcpy(&rep, &val, sizeof rep); 
    rep = htonl(rep); 
    memcpy(&val, &rep, sizeof rep); 
    return val; 
} 

ragionevolmente buon compilatore ottimizzerà via le due memcpy chiamate; sono presenti per sconfiggere ottimizzazioni di aliasing rigorose e eccessive, quindi questo risulta efficiente come il htonl più il sovraccarico di una singola chiamata di funzione.

+0

Grazie per la risposta. Solo per essere sicuro di capire - l'ipotesi di ordinare i byte nella mia risposta è dovuta all'utilizzo di 'ieee754.h'? –

+1

Sì, 'ieee754.h' presuppone che - o almeno, tutte le implementazioni che ho visto facciano (e come ho detto, potrebbe anche essere un'ipotesi universalmente valida nella nostra era moderna). –

+1

Non alcune architetture ARM hanno una strana follia mista-endiana? Comunque, grazie ancora per la tua risposta - è molto più facile da capire della mia, per lo meno! –

0

Come accennato nella domanda sopra, ho una soluzione al mio problema, ma non sono particolarmente legato ad esso, e accolgo con favore altre risposte, quindi lo sto postando qui piuttosto che nella domanda. In particolare, sembra probabile che sia lento, e non sono sicuro se rompa il rigoroso aliasing, tra gli altri potenziali problemi.

#include <ieee754.h> 

float 
htonf (float val) 
{ 
    union ieee754_float u; 
    float v; 
    uint8_t *un = (uint8_t *) &v; 

    u.f = val; 
    un[0] = (u.ieee.negative << 7) + ((u.ieee.exponent & 0xfe) >> 1); 
    un[1] = ((u.ieee.exponent & 0x01) << 7) + ((u.ieee.mantissa & 0x7f0000) >> 16); 
    un[2] = (u.ieee.mantissa & 0xff00) >> 8; 
    un[3] = (u.ieee.mantissa & 0xff); 
    return v; 
} 

float 
ntohf (float val) 
{ 
    union ieee754_float u; 
    uint8_t *un = (uint8_t *) &val; 

    u.ieee.negative = (un[0] & 0x80) >> 7; 
    u.ieee.exponent = (un[0] & 0x7f) << 1; 
    u.ieee.exponent += (un[1] & 0x80) >> 7; 
    u.ieee.mantissa = (un[1] & 0x7f) << 16; 
    u.ieee.mantissa += un[2] << 8; 
    u.ieee.mantissa += un[3]; 

    return u.f; 
} 
+0

Non penso che sia lento. –

+1

Grazie per il voto di fiducia. Ho detto che mi sto occupando di un * lotto * di dati? ;-) Cosa ti fa sembrare veloce? –

+0

Non utilizza operazioni costose, nessun loop, nessun salto. E non riesco a immaginare come potresti farcela a fare meno operazioni. Ma sono almeno altrettanto curioso di vedere proposte migliori. –

1

Ecco una routine di scrittura IEEE 754 portatile. Scriverà un doppio nel formato IEEE 754, indipendentemente dalla rappresentazione in virgola mobile sul computer host.

/* 
* write a double to a stream in ieee754 format regardless of host 
* encoding. 
* x - number to write 
* fp - the stream 
* bigendian - set to write big bytes first, elee write litle bytes 
*    first 
* Returns: 0 or EOF on error 
* Notes: different NaN types and negative zero not preserved. 
*   if the number is too big to represent it will become infinity 
*   if it is too small to represent it will become zero. 
*/ 
static 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, but 
    *there is no portable isnan() */ 
    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; 
    } 
    /* out of range. Set to infinity */ 
    else if (shift > 1023) 
    { 
     hilong = 1024 + ((1 << (expbits - 1)) - 1); 
     hilong <<= (31 - expbits); 
     hilong |= (sign << 31); 
     lowlong = 0; 
     goto writedata; 
    } 
    else 
     fnorm = fnorm - 1.0; /* take the significant bit off mantissa */ 

    /* 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 (for convenience) */ 
    hibits = (long)(significand/4294967296); 
    hilong = (sign << 31) | (exp << (31 - expbits)) | hibits; 
    x = significand - hibits * 4294967296; 
    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); 
} 
Problemi correlati