2016-10-18 1 views
3

Ich habe eine Frage über die pack754() Funktion definiert in Section 7.4 of Beej's Guide to Network Programming.Warum multiplizieren wir den normierten Bruch mit 0,5, um den Signifikanden in der IEEE 754-Darstellung zu erhalten?

Diese Funktion wandelt eine Gleitkommazahl f in seine IEEE 754-Darstellung in dem bits die Gesamtzahl von Bits ist, die Zahl zu repräsentieren und expbits ist die Anzahl von Bits verwendet nur den Exponenten darzustellen.

Ich bin besorgt darüber, mit einfacher Genauigkeit Zahlen schwimmend nur, so dass für diese Frage, bits als 32 angegeben und expbits als 8 angegeben. Dies bedeutet, dass 23 Bits verwendet werden, um den Signifikanden zu speichern (weil ein Bit das Vorzeichenbit ist).

Meine Frage bezieht sich auf diese Codezeile.

significand = fnorm * ((1LL<<significandbits) + 0.5f); 

Was ist die Rolle von + 0.5f in diesem Code?

Hier ist ein vollständiger Code, der diese Funktion verwendet.

#include <stdio.h> 
#include <stdint.h> // defines uintN_t types 
#include <inttypes.h> // defines PRIx macros 

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

int main(void) 
{ 
    float f = 3.1415926; 
    uint32_t fi; 

    printf("float f: %.7f\n", f); 

    fi = pack754(f, 32, 8); 
    printf("float encoded: 0x%08" PRIx32 "\n", fi); 

    return 0; 
} 

Welchen Zweck erfüllt + 0.5f in diesem Code?

+0

Irgendetwas sagt mir, dass Sie diese Zeile nicht korrekt kopiert haben. Insbesondere denke ich, dass Sie einen zusätzlichen Satz von Klammern hinzugefügt haben. – user3386109

+1

@ user3386109 Der referenzierte Link hat den gleichen Kommentar und den gleichen Code 'significand = fnorm * ((1LL << significandbits) + 0.5f);' und die gleichen Typen. Stimme zu, ob es besser funktionieren würde als 'significand = fnorm * (1LL << significandbits) + 0.5f; '. – chux

Antwort

3

Der Code ist ein falscher Rundungsversuch.

long double fnorm; 
long long significand; 
unsigned significandbits 
... 
significand = fnorm * ((1LL<<significandbits) + 0.5f); // bad code 

Der erste Hinweis von der Unrichtigkeit der f0.5f ist, die float angibt, ist eine unsinnige Einführung float in einer Routine mit long double f und fnorm angibt. float Math hat keine Anwendung in der Funktion.

Das Hinzufügen von 0.5f bedeutet jedoch nicht, dass der Code auf float math in (1LL<<significandbits) + 0.5f beschränkt ist. Siehe FLT_EVAL_METHOD, was zu höheren Präzisionszwischenergebnissen führen kann und den Codeautor beim Testen getäuscht hat.

Ein Rundungsversuch macht Sinn, da das Argument long double ist und die Zieldarstellungen schmaler sind. Hinzufügen eines 0.5 ist ein allgemeiner Ansatz - aber es ist nicht hier getan. IMO, der Mangel des Autors, der hier bezüglich 0.5f kommentiert, deutete an, dass die Absicht "offensichtlich" - nicht subtil, wenn auch falsch war.

Als commented, Bewegen des 0.5 näher zum Runden richtig sein, aber können einige mis führen in den Zusatz denken mit float Mathe getan wird, (es ist long double Mathe ein long double Produkt zu float bewirkt, dass die 0.5f Zugabe sein gefördert zu long double zuerst).

// closer to rounding but may mislead 
significand = fnorm * (1LL<<significandbits) + 0.5f; 

// better 
significand = fnorm * (1LL<<significandbits) + 0.5L; // or 0.5l or simply 0.5 

Zur Abrundung, ohne die bevorzugten <math.h> Runden Routinen wie rintl(), roundl(), nearbyintl(), llrintl() Aufruf, Hinzufügen der expliziten Typ 0,5 bei Abrundung noch ein schwacher Versuch ist. Es ist schwach, weil es in vielen Fällen falsch abgerundet wird. Der + 0.5 Trick beruht darauf, dass diese Summe genau ist.

Betrachten

long double product = fnorm * (1LL<<significandbits); 
long long significand = product + 0.5; // double rounding? 

product + 0.5 sich vor Abschneiden/Zuordnung zu long long durch eine Rundung gehen kann - in der Tat double rounding.

Am besten das richtige Werkzeug im C-Shed der Standardbibliotheksfunktionen verwenden.

significand = llrintl(fnorm * (1ULL<<significandbits)); 

Eine Ecke Fall mit dieser Abrundung ist, wo bleibt significand nun ein zu groß und significand , exp Bedürfnisse Anpassung ist. Wie auch von @Nayuki identifiziert, hat Code auch andere Nachteile. Es schlägt auch auf -0.0 fehl.

2

Die + 0.5f erfüllt keinen Zweck im Code und kann schädlich oder irreführend sein.

Der Ausdruck (1LL<<significandbits) + 0.5f ergibt eine float. Aber selbst für den kleinen Fall von significandbits = 23 für Gleitkomma einfacher Genauigkeit, der Ausdruck ergibt (float) (2 + 0,5), die auf genau 2 (rund die Hälfte gerade).

Ersetzen + 0.5f mit + 0.0f führt zu dem gleichen Verhalten. Hey, lass diesen Begriff ganz weg, denn fnorm wird dazu führen, dass das Argument der rechten Seite von * auf long double geworfen wird. Dies wäre ein besserer Weg, um die Linie zu umschreiben: long long significand = fnorm * (long double)(1LL << significandbits);


Randbemerkung: Diese Implementierung von pack754() Griffen Null korrekt (und bricht zusammen negativ Null auf positive Null), aber mishandles subnormale Zahlen (falsche Bits), Unendlichkeiten (Endlosschleife) und NaN (falsche Bits). Es ist am besten, es nicht als Referenzmodellfunktion zu behandeln.

+0

Wäre 'significand = fnorm * (1LL << significandbits) + 0.5f' stattdessen besser, weil es die Mantisse auf die nächste ganze Zahl abrunden würde? Wenn nicht, warum nicht? –

+1

Ich habe hier eine ähnliche Funktion. https://github.com/MalcolmMcLean/ieee754 –

+0

Was ist der runde Modus ist nicht "runde Hälfte sogar"? Ich vermute, dafür ist das 0.5f da. – chux

Verwandte Themen