2010-05-27 10 views
9

Gegeben zwei Gleitkommazahlen, ich suche nach einer effizienten Möglichkeit zu überprüfen, ob sie das gleiche Vorzeichen haben, gegeben, falls vorhanden der beiden Werte ist Null (+0,0 oder -0,0), sie sollten als gleiches Vorzeichen betrachtet werden.Wie man das Vorzeichen zweier Gleitkommawerte bei der Behandlung negativer Nullen effizient vergleicht

Zum Beispiel

  • SameSign (1.0, 2.0) sollte return true
  • SameSign (-1.0, -2.0) sollten wahre
  • SameSign (-1.0, 2.0) zurückgeben sollte return false
  • SameSign (0.0, 1.0) sollte return true
  • SameSign (0.0, -1.0) sollte true zurück
  • SameSign (-0.0, 1.0) sollte return true
  • SameSign (-0.0, -1.0) sollten

A naive aber korrekte Umsetzung der SameSign in C++ true zurück wäre:

bool SameSign(float a, float b) 
{ 
    if (fabs(a) == 0.0f || fabs(b) == 0.0f) 
     return true; 

    return (a >= 0.0f) == (b >= 0.0f); 
} 

die IEEE Unter der Annahme Gleitkommazahlen Modell, hier ist eine Variante von SameSign, die (zumindest mit Visual C++ 2008) branchless Code kompiliert:

bool SameSign(float a, float b) 
{ 
    int ia = binary_cast<int>(a); 
    int ib = binary_cast<int>(b); 

    int az = (ia & 0x7FFFFFFF) == 0; 
    int bz = (ib & 0x7FFFFFFF) == 0; 
    int ab = (ia^ib) >= 0; 

    return (az | bz | ab) != 0; 
} 

mit binary_cast wie folgt definiert:

template <typename Target, typename Source> 
inline Target binary_cast(Source s) 
{ 
    union 
    { 
     Source m_source; 
     Target m_target; 
    } u; 
    u.m_source = s; 
    return u.m_target; 
} 

Ich suche nach zwei Dinge:

  1. Eine schnellere, effizientere Implementierung von SameSign, mit Bit-Tricks, FPU Tricks oder sogar SSE-Intrinsics.

  2. Eine effiziente Erweiterung von SameSign zu drei Werten.

Edit:

ich einige Performance-Messungen an den drei Varianten von SameSign gemacht haben (die beiden Varianten in der ursprünglichen Frage beschrieben, sowie Stephen eins). Jede Funktion wurde 200-400 Mal auf allen aufeinanderfolgenden Wertepaaren in einem Array von 101 zufällig mit -1,0, -0,0, +0,0 und +1,0 aufgefüllten Schwimmern ausgeführt. Jede Messung wurde 2000 mal wiederholt und die Mindestzeit wurde eingehalten (um alle Cache-Effekte und systembedingten Verlangsamungen auszumerzen). Der Code wurde mit Visual C++ 2008 SP1 mit maximaler Optimierung und aktivierter SSE2-Codegenerierung kompiliert. Die Messungen wurden mit einem Core 2 Duo P8600 2,4 Ghz durchgeführt.

Hier werden die Zeitpunkte sind, nicht den Overhead des Abrufens Eingangswerte aus dem Array zu zählen, die Funktion aufrufen und das Ergebnis (in Höhe von 6-7 clockticks) Abrufen:

  • Naive Variante: 15 ticks
  • Bit magische Variante: 13 ticks
  • Stephens Variante: 6 ticks
+0

Jede bestimmte Sprache/Plattform? –

+0

Hey, danke für die gute Frage :) Vorzugsweise C/C++ auf x86. –

+0

mögliches Duplikat von [vergleicht zwei Floats, um zu sehen, ob sie beide negativ oder beide positiv sind.] (Http://stackoverflow.com/questions/2013680/comparting-two-floats-to-see-if-theyre-both Negativ oder positiv positiv – ChrisF

Antwort

10

Wenn Sie Unendlichkeiten nicht unterstützen müssen, können Sie j Bitte verwenden Sie:

das ist eigentlich ziemlich schnell auf den meisten modernen Hardware und ist komplett portabel. Im Fall von (null, unendlich) funktioniert es jedoch nicht richtig, weil Null * unendlich NaN ist und der Vergleich unabhängig von den Vorzeichen den Wert false zurückgibt. Es wird auch einen demormalen Stall auf etwas Hardware verursachen, wenn a und b beide winzig sind.

+0

In der Tat funktioniert das gut für zwei Werte und hat die richtige Semantik. Meine einzige Sorge ist, dass es drei Multiplikationen für den Fall mit drei Werten erfordert (a * b> = 0.0f && a * c> = 0.0f && b * c> = 0.0f). –

+0

@ François: Ja, der Drei-Wert-Fall ist ein interessantes Puzzle. Ich muss darüber nachdenken. –

+0

Ist das genau? Für mich wäre das die naheliegende Lösung, aber ich brauche auch ein genaues Ergebnis, unabhängig von Rundungsfehlern. Es scheint mir, dass a * b nach oben zu 0 gerundet werden kann und dann berechnet diese Funktion den falschen Wert. Nicht sicher, obwohl. – migle

3

vielleicht so etwas wie:

inline bool same_sign(float a, float b) { 
    return copysignf(a,b) == a; 
} 

Manpage für copysign für weitere Informationen zu sehen, was es tut (auch möchten Sie vielleicht, dass -0 überprüfen = 0!)

oder möglicherweise dieses wenn Sie C99 Funktionen haben

inline bool same_sign(float a, float b) { 
    return signbitf(a) == signbitf(b); 
} 

als eine Randnotiz, auf gcc zumindest beide copysign und Vorzeichenbit sind eingebaute Funktionen, so sollten sie schnell sein, wenn Sie sicher, dass Version der builtin machen wollen verwendet wird Sie können __builtin_signbitf (a)

EDIT tun: dies sollte auch einfach sein, wie auch auf den Wert 3 Fall zu verlängern (beide eigentlich davon sollte ...)

inline bool same_sign(float a, float b, float c) { 
    return copysignf(a,b) == a && copysignf(a,c) == a; 
} 

// trust the compiler to do common sub-expression elimination 
inline bool same_sign(float a, float b, float c) { 
    return signbitf(a) == signbitf(b) && signbitf(a) == signbitf(c); 
} 

// the manpages do not say that signbit returns 1 for negative... however 
// if it does this should be good, (no branches for one thing...) 
inline bool same_sign(float a, float b, float c) { 
    int s = signbitf(a) + signbitf(b) + signbitf(c); 
    return !s || s==3; 
} 
0

Einen kleinen Hinweis auf Vorzeichenbit: Das Makro gibt ein int zurück und die man-Seite gibt an, dass "es einen Wert ungleich Null zurückgibt, wenn der Wert von X sein Vorzeichenbit festgelegt hat". Dies bedeutet, dass die Spudd86 nicht garantiert funktioniert, wenn signbit zwei verschiedene Nicht-Null-Int für zwei verschiedene negative Werte zurückgibt.

gewährleistet zunächst einen korrekten Rückgabewert bool Casting:

inline bool same_sign(float a, float b) { 
    return (bool)signbitf(a) == (bool)signbitf(b); 
} 
Verwandte Themen