2010-05-27 18 views
9

Étant donné deux nombres à virgule flottante, je cherche un efficace façon de vérifier si elles ont le même signe, étant donné que si des deux valeurs est zéro (+0.0 ou -0.0), ils doivent être considérés comme ayant le même signe.Comment comparer efficacement le signe de deux valeurs à virgule flottante lors de la manipulation des zéros négatifs

Par exemple,

  • SameSign (1.0, 2.0) doit renvoyer true
  • SameSign (-1,0, -2,0) doit renvoyer true
  • SameSign (-1,0, 2.0) devrait renvoyer false
  • SameSign (0.0, 1.0) doit renvoyer true
  • SameSign (0,0, -1,0) doit retourner true
  • SameSign (-0,0, 1,0) doit retourner true
  • SameSign (-0,0, -1,0) doit renvoyer true

Une implémentation naïve mais correcte de SameSign en C++ serait:

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

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

en supposant que le modèle à virgule flottante IEEE, voici une variante de SameSign qui compile le code de branches (au moins avec 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; 
} 

avec binary_cast défini comme suit:

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

Je cherche deux choses:

  1. Une plus rapide, la mise en œuvre plus efficace des SameSign, en utilisant des astuces de bits, FPU astuces ou même SSE intrinsèques.

  2. Une extension efficace de SameSign à trois valeurs.

Edit:

J'ai fait quelques mesures de performance sur les trois variantes de SameSign (les deux variantes décrites dans la question initiale, plus Etienne un). Chaque fonction a été exécutée 200 à 400 fois, sur toutes les paires de valeurs consécutives dans un tableau de 101 flottants remplis au hasard avec -1,0, -0,0, +0,0 et +1,0. Chaque mesure a été répétée 2000 fois et le temps minimum a été conservé (pour éliminer tous les effets de cache et les ralentissements induits par le système). Le code a été compilé avec Visual C++ 2008 SP1 avec l'optimisation maximale et la génération de code SSE2 activée. Les mesures ont été effectuées sur un Core 2 Duo P8600 2.4 Ghz.

Voici les synchronisations, sans compter les frais généraux de l'extraction des valeurs d'entrée provenant du réseau, l'appel de la fonction et la récupération du résultat (qui se montent à 6-7 clockticks):

  • variante Naive: 15 tiques
  • Bit variante magique: 13 tiques
  • la variante de Stephens: 6 tiques
+0

Toute langue particulière/plate-forme? –

+0

Hé, merci pour la bonne question :) De préférence C/C++ sur x86. –

+0

duplication possible de [comparant deux flottants pour voir s'ils sont tous deux négatifs, ou les deux positifs.] (Http://stackoverflow.com/questions/2013680/comparing-two-floats-to-see-if-theyre-both -negative-or-both-positive) – ChrisF

Répondre

10

Si vous n'avez pas besoin de soutenir infinités, vous pouvez j uste utilisation:

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

qui est en fait assez rapidement sur la plupart du matériel moderne, et est complètement portable. Cependant, il ne fonctionne pas correctement dans le cas (zéro, infini), car zéro * infini est NaN, et la comparaison retournera false, quels que soient les signes. Il subira également un décrochage dénormal sur certains matériels lorsque a et b sont tous deux minuscules.

+0

En effet, cela fonctionne bien pour deux valeurs, et a la sémantique appropriée. Mon seul souci est qu'il nécessite trois multiplications pour le cas des trois valeurs (a * b> = 0.0f && a * c> = 0.0f && b * c> = 0.0f). –

+0

@ François: oui, le cas des trois valeurs est un puzzle intéressant. Je vais devoir y réfléchir un peu. –

+0

Est-ce exact? Pour moi, ce serait la solution évidente, mais je dois aussi avoir un résultat exact, indépendamment des erreurs d'arrondi. Il me semble que a * b peut être arrondi vers le haut vers 0 et que cette fonction calcule la mauvaise valeur. Pas sûr, cependant. – migle

3

peut-être quelque chose comme:

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

voir la page de manuel pour copysign pour plus d'informations sur ce qu'il fait (aussi vous voudrez peut-être vérifier que -0 = 0!)

ou peut-être cette si vous avez des fonctions C99

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

comme une note de côté, sur gcc au moins deux copysign et signbit sont les fonctions internes afin qu'ils devraient être rapide, si vous voulez vous assurer que la version builtin est utilisée vous pouvez le faire __builtin_signbitf (a)

EDIT: cela devrait également être facile à étendre au cas 3 de valeur ainsi (en fait ces deux devraient ...)

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

Une petite note sur signbit: La macro renvoie un int et la page de manuel indique que "Elle renvoie une valeur non nulle si la valeur de x a son bit de signe défini." Cela signifie que le de Spudd86 n'est pas garanti de fonctionner dans le cas où le signet renvoie deux int différents non-zéro pour deux valeurs négatives différentes.

casting bool assure d'abord une valeur de retour correcte:

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