2016-07-08 14 views
-1

Der folgende Codeausschnitt alle über das Internet gestreut und scheint mit sehr wenig Änderungen in mehreren verschiedenen Projekten verwendet werden:ULP Vergleichscode

union Float_t { 
    Float_t(float num = 0.0f) : f(num) {} 
    // Portable extraction of components. 
    bool Negative() const { return (i >> 31) != 0; } 
    int RawMantissa() const { return i & ((1 << 23) - 1); } 
    int RawExponent() const { return (i >> 23) & 0xFF; } 

    int i; 
    float f; 
}; 

inline bool AlmostEqualUlpsAndAbs(float A, float B, float maxDiff, int maxUlpsDiff) 
{ 
    // Check if the numbers are really close -- needed 
    // when comparing numbers near zero. 
    float absDiff = std::fabs(A - B); 
    if (absDiff <= maxDiff) 
     return true; 

    Float_t uA(A); 
    Float_t uB(B); 

    // Different signs means they do not match. 
    if (uA.Negative() != uB.Negative()) 
     return false; 

    // Find the difference in ULPs. 
    return (std::abs(uA.i - uB.i) <= maxUlpsDiff); 
} 

Siehe zum Beispiel here oder here oder here.

Allerdings verstehe ich nicht, was hier vor sich geht. Zu meinem (vielleicht naiven) Verständnis wird die Gleitkommaelementvariable f im Konstruktor initialisiert, aber das ganzzahlige Element i ist nicht.

Ich bin nicht sehr vertraut mit den binären Operatoren, die hier verwendet werden, aber ich kann nicht verstehen, wie Zugriffe von uA.i und uB.i produzieren alles andere als Zufallszahlen gegeben, dass keine Zeile im Code tatsächlich die Werte von f verbindet und i in irgendeiner sinnvollen Weise.

Wenn mir jemand aufklären könnte warum (und wie) genau dieser Code das gewünschte Ergebnis bringt, würde ich mich sehr freuen!

+2

sieht aus wie einige nicht portable Bittrickerei geht. – NathanOliver

+0

Ich stimme zu. Leider muss ich diesen Code umschreiben und aufräumen, also würde ich gerne verstehen, was genau zuerst passiert :-) – carsten

+0

@carsten, FYI, der Code stammt aus Bruce Dawsons [Blogserie über Fließkomma] (https : //randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). Eine vollständige Erklärung wird dort gegeben. – Pod

Antwort

4

Viele Undefined Behavior werden hier ausgenutzt. Die erste Annahme ist, dass Vereinigungsfelder anstelle von einander zugänglich sind, was in sich UB ist. Weiterhin nimmt der Kodierer an, dass: sizeof(int) == sizeof(float), Floats eine gegebene Länge von Mantisse und Exponent haben, dass alle Union-Elemente auf Null ausgerichtet sind, dass die binäre Darstellung von float genau mit der binären Darstellung mit int übereinstimmt. Kurz gesagt, das wird funktionieren, solange du auf x86 bist, spezifische Int- und Float-Typen hast und bei jedem Sonnenaufgang und Sonnenuntergang ein Gebet sagst.

Was Sie wahrscheinlich nicht bemerkt haben ist, dass dies eine Union ist, daher wird int i und float f normalerweise in einer bestimmten Weise in einem gemeinsamen Speicherarray von den meisten Compilern ausgerichtet. Dies ist im Allgemeinen immer noch UB und Sie können nicht einmal sicher davon ausgehen, dass die gleichen physikalischen Bits des Speichers verwendet werden, ohne sich auf einen spezifischen Compiler zu beschränken. Alles, was garantiert ist, ist, dass die Adresse beider Mitglieder die gleiche sein wird (aber es könnte Probleme mit der Ausrichtung und/oder der Schreibweise geben). Unter der Annahme, dass Ihr Compiler die gleichen physikalischen Bits verwendet (was durch den Standard keinesfalls garantiert ist) und beide bei Offset 0 beginnen und die gleiche Größe haben, wird i das binäre Speicherformat f darstellen .. solange nichts Änderungen in Ihrer Architektur. Ein Rat? Benutze es nicht, bis du es nicht musst. Bleiben Sie bei Gleitkommaoperationen für AlmostEquals(), Sie können es so implementieren. Es ist der allerletzte Durchlauf der Optimierung, wenn wir diese Besonderheiten berücksichtigen, und wir machen es normalerweise in einem separaten Zweig, Sie sollten Ihren Code nicht darum herum planen.

+0

Ich verstehe, dass ich dieses Stück Code am besten durch etwas wie diese austauschen (http://stackoverflow.com/questions/4010240/comparing-doubles), aber ich muss die Schnittstellenkompatibilität beibehalten, also müsste ich verstehen, was die Argumente genau tun, z wie man 'maxUlpsDiff' in etwas Portables übersetzt. Möchten Sie das kommentieren? – carsten

+1

Schlechter - Typ-Punning, also das Lesen eines Typs, als ob es ein anderer Typ wäre, ist in C++ einfach nicht erlaubt. Dies beinhaltet die Verwendung von "union", um dies zu versuchen. In C können Sie dafür eine "Union" verwenden. In C++ ** ist es reine UB **.Selbst wenn die Bitdarstellungen der Typen garantiert wären, würde dies diesen Code nicht speichern. Es ist nicht speicherbar. Es könnte bei den meisten Compilern funktionieren, die sich nicht darum kümmern müssen, dass sie stattdessen Ihren Rasen mähen, aber es ist ein nicht portierbarer Hack und sollte daher direkt an '/ dev/null' übergeben werden. –

+0

@underscore_d: Bearbeitet, um das zu klären, danke :). – lorro