2010-05-27 7 views
9

Dado dos números de punto flotante, estoy buscando un eficiente forma de comprobar si tienen el mismo signo, dado que, de haberlo, de los dos valores es cero (+0.0 o -0.0), se debe considerar que tienen el mismo signo.Cómo comparar eficientemente el signo de dos valores de punto flotante mientras se manejan ceros negativos

Por ejemplo,

  • SameSign (1.0, 2.0) debe devolver verdadero
  • SameSign (-1,0, -2,0) debe devolver verdadero
  • SameSign (-1,0, 2,0) debe devolver falsa
  • SameSign (0.0, 1.0) debería devolver cierto
  • SameSign (0,0, -1,0) debe devolver verdadero
  • SameSign (-0,0, 1,0) debería devolver cierto
  • SameSign (-0,0, -1,0) debe devolver verdadero

Una implementación ingenua pero correcta de SameSign en C++ sería:

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

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

Suponiendo que el modelo de punto flotante IEEE, he aquí una variante de SameSign que compila a código sin sucursales (al menos con 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; 
} 

con binary_cast define de la siguiente manera:

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

Busco dos cosas:

  1. una implementación más rápida, más eficiente de SameSign, usando trucos bits, FPU trucos o incluso intrínseca SSE.

  2. Una extensión eficiente de SameSign a tres valores.

Editar:

He hecho algunas mediciones de rendimiento en las tres variantes de SameSign (las dos variantes descritas en la pregunta original, además de Stephen uno). Cada función se ejecutó de 200 a 400 veces, en todos los pares consecutivos de valores en una matriz de 101 flotantes llenos al azar con -1.0, -0.0, +0.0 y +1.0. Cada medición se repitió 2000 veces y se mantuvo el tiempo mínimo (para descartar todos los efectos de caché y las ralentizaciones inducidas por el sistema). El código se compiló con Visual C++ 2008 SP1 con la optimización máxima y la generación de código SSE2 habilitada. Las mediciones se realizaron en un Core 2 Duo P8600 2.4 Ghz.

Aquí son los tiempos, sin contar la sobrecarga de ir a buscar valores de entrada de la matriz, llamando a la función y recuperar el resultado (que ascienden a 6-7 clockticks):

  • variante Naive: 15 garrapatas
  • Bit variante de la magia: 13 garrapatas
  • variante de Stephens: 6 garrapatas
+0

Cualquier lenguaje/plataforma en particular? –

+0

Oye, gracias por la buena pregunta :) Preferiblemente C/C++ en x86. –

+0

posible duplicado de [comparación de dos flotantes para ver si ambos son negativos, o ambos positivos.] (Http://stackoverflow.com/questions/2013680/comparing-two-floats-to-see-if-theyre-both -negativo-o-ambos-positivo) – ChrisF

Respuesta

10

Si no es necesario para apoyar infinitos, se puede j ust use:

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

que en realidad es bastante rápido en hardware más moderno, y es completamente portátil. Sin embargo, no funciona correctamente en el caso (cero, infinito) porque cero * infinito es NaN, y la comparación dará como resultado falso, independientemente de los signos. También incurrirá en un bloqueo denormal en algún hardware cuando a y b son pequeños.

+0

De hecho, esto funciona bien para dos valores, y tiene la semántica adecuada. Mi única preocupación es que requiere tres multiplicaciones para el caso de los tres valores (a * b> = 0.0f && a * c> = 0.0f && b * c> = 0.0f). –

+0

@ François: sí, el caso de los tres valores es un acertijo interesante. Tendré que pensarlo un poco. –

+0

¿Es esto exacto? Para mí, esta sería la solución obvia, pero también necesito tener un resultado exacto independientemente de los errores de redondeo. Me parece que a * b se puede redondear hacia 0 y luego esta función calcula el valor incorrecto. No estoy seguro, sin embargo. – migle

3

tal vez algo como:

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

ver la página del manual de copysign para obtener más información sobre lo que hace (también es posible que desee comprobar que -0 = 0!)

o posiblemente este si tiene funciones C99

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

como nota al margen, en gcc al menos tanto copysign y se signbit orden interna funciones por lo que debe ser rápido, si usted quiere asegurarse de que la versión incorporada está siendo utilizado usted puede hacer __builtin_signbitf (a)

EDIT: esto también debe ser fácil de extender al caso 3 Valor así (en realidad ambas cosas deberían ...)

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

Una pequeña nota sobre signbit: La macro devuelve un int y la página man indica que "Devuelve un valor distinto de cero si el valor de x tiene su bit de signo establecido". Esto significa que no se garantiza que el de Spudd86 funcione en caso de que signbit devuelva dos int diferentes distintas de cero para dos valores negativos diferentes.

La conversión a bool primera asegura un valor de retorno correcta:

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