2012-05-16 9 views
10

Ich habe kürzlich an einem System gearbeitet, das große Datenmengen speichern und laden muss, einschließlich Gleitkommawerte mit einfacher Genauigkeit. Ich entschied mich für Netzwerk-Byte-Reihenfolge für ganze Zahlen zu standardisieren, und beschlossen, auch Punktwerte im Big-Endian-Format zu speichern, schwimmend, dh:Portable Serialisierung von Gleitkommawerten nach IEEE754

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

Idealerweise möchte ich Funktionen bieten wie htonl() und ntohl(), da habe ich schon Ich benutze diese für das Abtupfen von Ganzzahlen, und ich möchte dies auch auf eine Weise implementieren, die so plattformunabhängig wie möglich ist (unter der Annahme, dass der Typ float IEEE754 32-Bit Fließkommawerten entspricht). Gibt es einen Weg, möglicherweise mit ieee754.h, um dies zu tun?

Ich habe eine Antwort, dass scheint zu funktionieren, und ich werde es unten schreiben, aber es scheint ziemlich langsam und ineffizient, und ich würde irgendwelche Vorschläge zu schätzen weiß, wie es schneller zu machen und/oder zuverlässiger.

+0

Was ist damit: http://stackoverflow.com/a/2782742/1327576? – smocking

+0

Ich schaute auf diese Antwort und es hängt eindeutig von der Annahme ab, dass die Host-Repräsentation Little-Endian ist. Ich suche nach etwas, das Host-Byte-Order-Agnostic ist. –

+0

Wahrscheinlich 'snprintf (b, sizeof (b),"% .9001f ", yourvalue)' (textbasierte Darstellung) ist am tragbarsten. –

Antwort

6

Viel einfacher und in Abhängigkeit von der gleichen Annahme wie Sie (die ist, dass die Schwimmer und Integer-Typen die gleiche Byte-Reihenfolge haben, und ist fast allgemeingültig - realistisch werden Sie nie ein anzutreffen, wo es ist nicht wahr):

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

Jeder einigermaßen guter Compiler die beiden memcpy Anrufe optimieren weg; Sie sind vorhanden, um übereifrige strenge Aliasing-Optimierungen zu besiegen, so dass dies so effizient wie der Overhead eines einzelnen Funktionsaufrufs ist.

+0

Danke für Ihre Antwort. Nur um sicherzustellen, dass ich verstehe - ist die Byte-Reihenfolge Annahme in meiner Antwort aufgrund der Verwendung von 'ieee754.h'? –

+1

Ja, 'ieee754.h' nimmt das an - oder zumindest alle Implementierungen, die ich gesehen habe (und wie gesagt, es könnte auch eine allgemeingültige Annahme in unserer modernen Ära sein). –

+1

Haben einige ARM-Architekturen nicht eine seltsame Mischung aus Endian-Verrücktheiten? Wie auch immer, nochmals vielen Dank für Ihre Antwort - es ist viel einfacher zu verstehen als meine, zumindest! –

0

Wie in der obigen Frage erwähnt, habe ich eine Lösung für mein Problem, aber ich bin nicht besonders daran gebunden, und ich begrüße andere Antworten, also poste ich es hier anstatt in der Frage. Insbesondere scheint es wahrscheinlich, langsam zu sein, und ich bin nicht sicher, ob es strikte Aliasing unter anderen möglichen Problemen bricht.

#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

Ich glaube nicht, dass das langsam ist. –

+1

Danke für das Vertrauensvotum. Habe ich erwähnt, dass es sich um eine Menge Daten handelt? ;-) Was lässt dich schnell aussehen? –

+0

Es verwendet keine teuren Operationen, keine Schleifen, keine Sprünge. Und ich kann mir nicht vorstellen, wie Sie mit wesentlich weniger Operationen durchkommen könnten. Aber ich bin mindestens so neugierig wie Sie, um bessere Vorschläge zu sehen. –

1

Hier ist eine tragbare IEEE 754 Schreibroutine. Es wird ein Double in IEEE 754-Format schreiben, unabhängig von der Gleitkommadarstellung auf dem Hostcomputer.

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