2011-01-19 14 views
44

Lo standard C++ non discute il layout sottostante di tipi float e double, ma solo l'intervallo di valori che dovrebbero rappresentare. (Questo vale anche per i tipi firmati, è il complimento di due o qualcos'altro)Portabilità della serializzazione binaria di tipo double/float in C++

La mia domanda è: quali sono le tecniche utilizzate per serializzare/deserializzare i tipi di POD come double e float in modo portatile? Al momento sembra che l'unico modo per fare ciò sia avere il valore rappresentato letteralmente (come in "123.456"), Il layout ieee754 per il doppio non è standard su tutte le architetture.

+2

Se avete bisogno di archiviazione di file, HDF5 o NetCDF di grande aiuto. – Anycorn

+5

Ottima domanda. –

risposta

1

Dovresti convertirli in un formato che sarai sempre in grado di utilizzare per ricreare i tuoi float/doppi.

Questo potrebbe usare una rappresentazione stringa o, se avete bisogno di qualcosa che richiede meno spazio, rappresenti il ​​tuo numero in IEEE754 (o qualsiasi altro formato che si sceglie) e poi parse come si farebbe con una stringa.

+2

Esistono librerie che accettano il doppio e convertono in uno specifico formato binario? al momento tutto quello che stiamo facendo è scrivere il layout in-memory sul disco che è ok, ma in un ambiente eterogeneo non funzionerà altrettanto bene. –

+0

Immagino ce ne siano alcuni, ma non ne conosco nessuno, mi dispiace. – peoro

5

Cosa c'è di sbagliato in un formato leggibile dall'uomo.

ha un paio di vantaggi rispetto binario:

  • E 'leggibile
  • è portatile
  • Rende supporto davvero facile
    (come si può chiedere all'utente di vedere le cose nella loro preferito editor di parole pari)
  • È facile correggere
    (o regolare i file manualmente in situazioni di errore)

Svantaggio:

  • Non è compatto
    Se questo è un problema reale si può sempre chiudere la cerniera.
  • Potrebbe essere leggermente più lento di estrarre/generate
    Nota un formato binario probabilmente ha bisogno di essere normalizzati così (vedi htonl())

Per emettere un doppio a piena precisione:

double v = 2.20; 
std::cout << std::setprecision(std::numeric_limits<double>::digits) << v; 

OK. Non sono convinto che sia esattamente preciso. Potrebbe perdere precisione.

+5

Ulteriore svantaggio: non è preciso. L'importanza di questo può variare notevolmente tra le applicazioni. –

+0

@Magnus Hoff: In che modo non è preciso? Se si emette il valore con tutta la precisione e la destinazione utilizza la stessa precisione, non si perderà nulla. Se la destinazione non utilizza la stessa precisione, tutte le puntate sono disattivate, ma non vi è alcuna perdita tra questo e un formato binario. –

+1

+1 anche se potrebbero esserci altri svantaggi: è più costoso generare/analizzare - influenzerà solo le prestazioni nelle applicazioni che principalmente leggono/scrivono i dati, ma comunque. Anche le dimensioni influiscono su di esse e zip-ping peggiorerà le prestazioni anche ... Tuttavia, una buona soluzione in * quasi tutti i casi * nel mondo reale il 99,9% delle volte. –

2

Creare un'interfaccia serializzatore/de-serializzatore appropriata per la scrittura/lettura di questo.

L'interfaccia può quindi avere diverse implementazioni e puoi testare le tue opzioni.

Come detto prima, le opzioni ovvia sarebbe:

  • IEEE754 che scrive/legge il pezzo binario se direttamente supportato dall'architettura o lo analizza se non supportata dall'architettura
  • Testo: ha sempre bisogno di analizzare.
  • Qualunque cosa tu possa pensare.

Basta ricordare: una volta che si dispone di questo livello, è sempre possibile iniziare con IEEE754 se si supportano solo piattaforme che utilizzano questo formato internamente. In questo modo avrai uno sforzo aggiuntivo solo quando dovrai supportare una piattaforma diversa! Non fare il lavoro che non devi.

27

Brian "Beej Jorgensen" Sala dà nel suo Guide to Network Programming qualche codice per il confezionamento di float (risp. double) per uint32_t (risp. uint64_t) per essere in grado di trasmettere in modo sicuro attraverso la rete tra due macchina che non sia può accettare di la loro rappresentazione Ha alcune limitazioni, principalmente non supporta NaN e infinito.

ecco la sua funzione di imballaggio:

#define pack754_32(f) (pack754((f), 32, 8)) 
#define pack754_64(f) (pack754((f), 64, 11)) 

uint64_t pack754(long double f, unsigned bits, unsigned expbits) 
{ 
    long double fnorm; 
    int shift; 
    long long sign, exp, significand; 
    unsigned significandbits = bits - expbits - 1; // -1 for sign bit 

    if (f == 0.0) return 0; // get this special case out of the way 

    // check sign and begin normalization 
    if (f < 0) { sign = 1; fnorm = -f; } 
    else { sign = 0; fnorm = f; } 

    // 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--; } 
    fnorm = fnorm - 1.0; 

    // calculate the binary form (non-float) of the significand data 
    significand = fnorm * ((1LL<<significandbits) + 0.5f); 

    // get the biased exponent 
    exp = shift + ((1<<(expbits-1)) - 1); // shift + bias 

    // return the final answer 
    return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand; 
} 
+5

non dovrebbe essere difficile includere NaN, infinito e numeri denormalizzati se ne hai bisogno. Inoltre questo codice è di dominio pubblico, il che lo rende un'ottima risposta. –

+3

Un approccio 'frexp' dovrebbe essere costantemente più veloce della ripetizione/moltiplicazione in virgola mobile ripetuta? 'frexp' fornisce' exp' e 'fnorm' in una singola chiamata. Tieni presente che il doppio IEEE 754 ha 11 bit di esponente, quindi potresti dividerlo/moltiplicarlo per 2 diverse centinaia di volte. – jw013

+1

@ jw013 Che aspetto avrebbe un approccio 'frexp' in questa situazione? Sto lottando con la serializzazione a virgola mobile ora, e mentre l'approccio 'frexp' sembra interessante, non riesco a capire come convertire la mantissa (che è tra 0.5 e 1) nella serie di bit che rappresentano il significato e in un IEEE galleggia o raddoppia. Esiste un modo efficiente e portabile per farlo? I dati di mercato –

4

Basta scrivere la rappresentazione IEEE754 binaria su disco, e documentare questo come formato di memorizzazione (insieme è endianness). Quindi spetta all'implementazione convertire questo in una sua rappresentazione interna, se necessario.

0

Penso che la risposta "dipenda" da ciò che è la tua particolare applicazione e il suo profilo di perfomance.

Supponiamo di disporre di un ambiente di dati di mercato a bassa latenza, quindi utilizzare le stringhe è francamente stupido. Se le informazioni che stai comunicando sono prezzi, allora i doppi (e la loro rappresentazione binaria) sono davvero difficili da gestire. Dove, se non ti interessa davvero le prestazioni e ciò che vuoi è la visibilità (archiviazione, trasmissione), le stringhe sono un candidato ideale.

Effettivamente opterei per la rappresentazione integrale di mantissa/esponente di float/doubles, ovvero alla prima opportunità, convertire il float/double in una coppia di interi e quindi trasmetterlo. Devi solo preoccuparti della portabilità degli interi e del bene, varie routine (come le routine hton() per gestire le conversioni per te). Memorizza anche tutto nella endianità della tua piattaforma più diffusa (ad esempio se stai usando solo Linux, allora qual è lo scopo di archiviare roba in big endian?)

+2

sono un cattivo esempio: il recupero dei dati di mercato è solitamente più costoso rispetto all'analisi di un gruppo di stringhe. Dipende dalla tua tecnologia, ma di solito tali cose sono memorizzate in un database. –

+0

@Alex, eh?Penso che potresti aver frainteso me, quando parlo di ambienti a bassa latenza, non sto parlando di dati storici - che potrebbero essere nei DB, ma ambienti di trading in cui ogni microsecondo conta - in quelli, vuoi davvero aggiungere ulteriore ritardo nelle routine di conversione delle stringhe? 'atoi()', 'scanf()', 'sprintf()', qualsiasi cosa sia relativamente lenta ... – Nim

+0

Penso che dovresti comprare hardware più veloce allora (cioè memoria più veloce). L'elaborazione delle stringhe è abbastanza veloce rispetto alla CPU, molto più veloce del recupero della stringa dalla memoria ... –

3

Dai un'occhiata all'implementazione del file (vecchio) gtypes.h in glib 2 - include il seguente:

#if G_BYTE_ORDER == G_LITTLE_ENDIAN 
union _GFloatIEEE754 
{ 
    gfloat v_float; 
    struct { 
    guint mantissa : 23; 
    guint biased_exponent : 8; 
    guint sign : 1; 
    } mpn; 
}; 
union _GDoubleIEEE754 
{ 
    gdouble v_double; 
    struct { 
    guint mantissa_low : 32; 
    guint mantissa_high : 20; 
    guint biased_exponent : 11; 
    guint sign : 1; 
    } mpn; 
}; 
#elif G_BYTE_ORDER == G_BIG_ENDIAN 
union _GFloatIEEE754 
{ 
    gfloat v_float; 
    struct { 
    guint sign : 1; 
    guint biased_exponent : 8; 
    guint mantissa : 23; 
    } mpn; 
}; 
union _GDoubleIEEE754 
{ 
    gdouble v_double; 
    struct { 
    guint sign : 1; 
    guint biased_exponent : 11; 
    guint mantissa_high : 20; 
    guint mantissa_low : 32; 
    } mpn; 
}; 
#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ 
#error unknown ENDIAN type 
#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ 

glib link

0

lo SQLite4 utilizza un nuovo formato per memorizzare doppie e galleggia

  • Funziona in modo affidabile e coerente anche su piattaforme prive di supporto per i numeri in virgola mobile binario64 IEEE 754.
  • I calcoli di valuta possono normalmente essere eseguiti esattamente e senza arrotondamento.
  • Qualsiasi numero intero a 64 bit con segno o senza segno può essere rappresentato esattamente.
  • L'intervallo in virgola mobile e la precisione superano quello dei numeri in virgola mobile binario64 IEEE 754.
  • L'infinito positivo e negativo e il NaN (non-un-numero) hanno rappresentazioni ben definite.

Fonti:

https://sqlite.org/src4/doc/trunk/www/design.wiki

https://sqlite.org/src4/doc/trunk/www/decimal.wiki

Problemi correlati