2010-05-27 10 views
9

Szukam dwóch liczb zmiennoprzecinkowych, szukam sposobu wydajnego, aby sprawdzić, czy mają ten sam znak, biorąc pod uwagę, że jeśli jakieś z dwóch wartości wynosi zero (+0,0 lub -0,0), należy uznać, że mają ten sam znak.Jak skutecznie porównywać znak dwóch wartości zmiennoprzecinkowych podczas obsługi zer ujemnych

Przykładowo

  • SameSign (1,0, 2,0) powinien wrócić prawdziwego
  • SameSign (-1.0, 2,0) powinien wrócić prawdziwego
  • SameSign (-1.0 2,0) powinien zwraca fałsz
  • SameSign (0,0, 1,0), należy zwrócić prawdziwego
  • SameSign (0,0, -1,0), należy zwrócić prawda
  • SameSign (-0,0, 1,0) powinien wrócić prawdziwego
  • SameSign (-0.0, -1,0), należy zwrócić prawda

naiwny jednak właściwe stosowanie SameSign C++ może być:

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

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

Zakładając zmiennoprzecinkowej IEEE modelu, oto wariant SameSign który kompiluje się do kodu branchless (przynajmniej z Visual C++ 2008):

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

z binary_cast zdefiniowane następująco:

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

Szukam dwóch rzeczy:

  1. Szybsza, bardziej efektywne wdrożenie SameSign, wiertłem sztuczek, FPU sztuczki, a nawet wewnętrzne SSE.

  2. Skuteczne rozszerzenie SameSign na trzy wartości.

Edit:

Zrobiłem kilka pomiarów wydajności na trzech wariantów SameSign (dwóch wariantów opisanych w oryginalne pytanie, plus Stephen jeden). Każda funkcja była uruchamiana 200-400 razy, na wszystkich kolejnych parach wartości w tablicy 101 płynów wypełnionych losowo z -1,0, -0,0, +0,0 i +1,0. Każdy pomiar powtórzono 2000 razy i utrzymano minimalny czas (aby wyeliminować wszystkie efekty pamięci podręcznej i spowolnienia wywołane przez system). Kod został skompilowany z Visual C++ 2008 SP1 z maksymalną optymalizacją i włączonym generowaniem kodu SSE2. Pomiary przeprowadzono na Core 2 Duo P8600 2,4 Ghz.

Oto czasy nie licząc napowietrznej pobierania wartości wejściowych z tablicy wywoływania odzyskiwania funkcji i wynik (które wynoszą 6-7 clockticks):

  • wariant Naiwne 15 kleszczy
  • Bit magia wariant: 13 kleszczy
  • Stephens za wariant: 6 kleszcze
+0

jakiś szczególny język/platformy? –

+0

Hej, dzięki za dobre pytanie :) Najlepiej C/C++ na x86. –

+0

możliwy duplikat [porównywania dwóch elementów pływających, aby sprawdzić, czy oba są ujemne, czy oba są dodatnie.] (Http://stackoverflow.com/questions/2013680/porównanie-dwa-przypadków-do-widoczenia-wysokie-obszary -negative-or-both-positive) – ChrisF

Odpowiedz

10

Jeśli nie muszą wspierać nieskończoności, można j ust używać:

inline bool SameSign(float a, float b) { 
    return a*b >= 0.0f; 
} 

, który jest dość szybki na większości nowoczesnych urządzeń i jest całkowicie przenośny. Nie działa jednak poprawnie w przypadku (zero, nieskończoność), ponieważ zero * nieskończoność jest NaN, a porównanie zwróci wartość false, niezależnie od znaków. Będzie również powodować denormalne przeciągnięcie na niektórych urządzeniach, gdy a i b są małe.

+0

Rzeczywiście, działa to dobrze dla dwóch wartości i ma właściwą semantykę. Moim jedynym zmartwieniem jest to, że wymaga trzech multiplikacji dla przypadku trzech wartości (a * b> = 0.0f && a * c> = 0.0f && b * c> = 0.0f). –

+0

@ François: tak, sprawa o trzech wartościach jest interesującą zagadką. Będę musiał trochę o tym pomyśleć. –

+0

Czy to jest dokładne? Dla mnie byłoby to oczywiste rozwiązanie, ale muszę mieć dokładny wynik niezależnie od błędów zaokrąglania. Wydaje mi się, że a * b może być zaokrąglone w górę w kierunku 0, a następnie ta funkcja wylicza złą wartość. Nie jestem jednak pewien. – migle

3

może coś takiego:

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

zobaczyć stronę man copysign aby uzyskać więcej informacji na temat tego, co robi (również może chcieć sprawdzić, -0 = +0!)

lub ewentualnie ten jeśli masz funkcje C99

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

jak marginesie, na gcc przynajmniej zarówno copysign i signbit są wbudowane funkcje, więc powinny być szybki, jeśli chcesz, aby upewnić się, że wersja wbudowana jest używany można zrobić __builtin_signbitf (a)

EDIT: to powinno być również łatwo rozszerzyć na przypadek 3 wartości, jak również (w rzeczywistości oba te powinny ...)

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

mała uwaga na signbit: Makro zwraca int, a strona man stwierdza, że ​​"zwraca niezerową wartość, jeśli wartość x ma ustawiony bit znaku." Oznacza to, że nie można zagwarantować, że znak Spudd86 zadziała w przypadku, gdy signbit zwróci dwie różne niezerowe wartości int dla dwóch różnych wartości ujemnych.

Casting do bool pierwszy zapewnia prawidłową wartość powrotu:

inline bool same_sign(float a, float b) { 
    return (bool)signbitf(a) == (bool)signbitf(b); 
} 
Powiązane problemy