2008-08-21 11 views
9

¿Cuál es el mejor método para comparar IEEE flota y dobla para igualdad? He oído hablar de varios métodos, pero quería ver qué pensaba la comunidad.Comparación de flotantes IEEE y dobles para igualdad

+0

Pedido esta [respuesta] (http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison#17467) a una pregunta similar. – grom

Respuesta

7

El mejor enfoque, creo, es comparar ULPs.

bool is_nan(float f) 
{ 
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0; 
} 

bool is_finite(float f) 
{ 
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000; 
} 

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point) 
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other 
#define UNEQUAL_NANS 1 
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater) 
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX 
#define INFINITE_INFINITIES 1 

// test whether two IEEE floats are within a specified number of representable values of each other 
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers 
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference) 
{ 
#ifdef UNEQUAL_NANS 
    if(is_nan(lhs) || is_nan(rhs)) 
    { 
     return false; 
    } 
#endif 
#ifdef INFINITE_INFINITIES 
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs))) 
    { 
     return false; 
    } 
#endif 
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs)); 
    // transform signed magnitude ints into 2s complement signed ints 
    if(left < 0) 
    { 
     left = 0x80000000 - left; 
    } 
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs)); 
    // transform signed magnitude ints into 2s complement signed ints 
    if(right < 0) 
    { 
     right = 0x80000000 - right; 
    } 
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference) 
    { 
     return true; 
    } 
    return false; 
} 

Una técnica similar se puede utilizar para dobles. El truco es convertir los flotadores para que estén ordenados (como enteros) y luego ver qué tan diferentes son.

No tengo idea de por qué esta maldita cosa está arruinando mis guiones bajos. Editar: Oh, tal vez eso es solo un artefacto de la vista previa. Eso está bien entonces.

+0

Esto gana con la mano hacia abajo para mayor precisión. Pero para el rendimiento ... tendrías que intercambiar un poco de esa precisión para una mejora de velocidad. ¿Convenido? –

+1

Si necesita duplicación o portabilidad: Encontré una gran implementación multiplataforma de esto que puede tratar tanto con dobles como flotantes en Google Test y lo publicó aquí: http://stackoverflow.com/questions/17333/most-effective- camino-para-flotar-y-doble-comparación/3423299 # 3423299 – skrebbel

0

Oh querido señor, por favor no interpretes los bits de flotador como si fueses a menos que estés ejecutando un P6 o anterior.

0

Oh querido señor, por favor no interprete los bits del flotador como si no estuvieran ejecutando un P6 o una versión anterior.

Incluso si se hace que se copie a partir del vector registra a entero registros a través de la memoria, e incluso si se atasca la tubería, que es la mejor manera de hacerlo que me he encontrado, en la medida que proporciona la mayor comparaciones robustas incluso frente a los errores de coma flotante.

es decir, es un precio que vale la pena pagar.

+0

Sospecho que '(a == b || (a! = a && b! = b))' en muchos procesadores será más rápido que cualquier cosa que implique flotador -bits-to-int lanzamientos, aunque realmente me duele que expresiones como las anteriores sean necesarias. Me pregunto cuánto beneficio ofrece la decisión Nan! = NaN, y cuánto ha costado en tiempo desperdiciado de código y depuración. – supercat

3

La versión actual que estoy usando es este

bool is_equals(float A, float B, 
       float maxRelativeError, float maxAbsoluteError) 
{ 

    if (fabs(A - B) < maxAbsoluteError) 
    return true; 

    float relativeError; 
    if (fabs(B) > fabs(A)) 
    relativeError = fabs((A - B)/B); 
    else 
    relativeError = fabs((A - B)/A); 

    if (relativeError <= maxRelativeError) 
    return true; 

    return false; 
} 

Esto parece hacerse cargo de la mayoría de los problemas mediante la combinación de tolerancia de error relativo y absoluto. ¿El enfoque de ULP es mejor? Si es así, ¿por qué?

0

es la mejor manera de hacerlo que he encontrado, en la medida en que proporciona las comparaciones más robustas, incluso frente a los errores de coma flotante.

Si tiene errores de coma flotante, tiene aún más problemas que esto. Aunque supongo que eso depende de la perspectiva personal.

+1

SIEMPRE tendrá errores de coma flotante. – BCS

0

Esto parece resolver la mayoría de los problemas combinando tolerancia de error relativa y absoluta. ¿El enfoque de ULP es mejor? Si es así, ¿por qué?

Las ULP son una medida directa de la "distancia" entre dos números de coma flotante. Esto significa que no requieren que invoque los valores de error relativos y absolutos, ni tiene que asegurarse de obtener esos valores "sobre la derecha". Con los ULP, puede expresar directamente qué tan cerca quiere que sean los números, y el mismo umbral funciona igual de bien para los valores pequeños que para los grandes.

0

Si tiene errores de punto flotante, tiene incluso más problemas que esto. Aunque supongo que eso depende de la perspectiva personal.

Incluso si hacemos el análisis numérico para minimizar la acumulación de errores, no podemos eliminarlo y que se puede dejar con resultados que deben ser idénticos (si nos Cálculo con números reales) pero difieren (porque no se puede calcular con reales).

0

Si está buscando dos flotadores para ser iguales, entonces deberían ser idénticos en mi opinión. Si se enfrenta a un problema de redondeo de punto flotante, tal vez una representación de punto fijo se adapte mejor a su problema.

+0

que trata de (casi) la igualdad es un caso común en los bucles en los que desea detener cuando las cosas se "acercan lo suficiente", pero donde no espera que converjan exactamente. – BCS

0

Si está buscando dos flotadores iguales, entonces deberían ser idénticos en mi opinión. Si se enfrenta a un problema de redondeo de punto flotante, tal vez una representación de punto fijo se adapte mejor a su problema.

Tal vez no podemos permitirnos la pérdida de alcance o rendimiento que tal enfoque infligiría.

0

Si está buscando dos flotadores iguales, entonces deberían ser idénticos en mi opinión. Si se enfrenta a un problema de redondeo de punto flotante, tal vez una representación de punto fijo se adapte mejor a su problema.

Quizás debería explicar mejor el problema. En C++, el siguiente código:

#include <iostream> 

using namespace std; 


int main() 
{ 
    float a = 1.0; 
    float b = 0.0; 

    for(int i=0;i<10;++i) 
    { 
    b+=0.1; 
    } 

    if(a != b) 
    { 
    cout << "Something is wrong" << endl; 
    } 

    return 1; 
} 

imprime la frase "Algo está mal". ¿Estás diciendo que debería?

+0

Casi siempre obtendrá a! = B debido a la precisión del punto flotante y los errores de redondeo. –

0

@DrPizza: No soy un gurú del rendimiento pero espero que las operaciones de punto fijo sean más rápidas que las operaciones de coma flotante (en la mayoría de los casos).

@Craig H: Sure. Estoy totalmente de acuerdo con que imprima eso. Si a o b almacena dinero, deberían estar representados en un punto fijo. Estoy luchando por pensar en un ejemplo del mundo real donde tal lógica deba estar aliada a las carrozas. Cosas adecuados para los flotadores:

  • pesos
  • filas
  • distancias
  • valores del mundo real (como el de un ADC)

Por todas estas cosas, ya sea que mucho, entonces los números y simplemente presentar los resultados al usuario para la interpretación humana, o hacer una declaración comparativa (incluso si tal afirmación es, "esto está dentro de 0.001 de esta otra cosa"). Una afirmación comparativa como la mía solo es útil en el contexto del algoritmo: la parte "dentro de 0.001" depende de la pregunta física que está solicitando. Eso es mi 0.02. ¿O debería decir 2/100ths?

1

@DrPizza: No soy un gurú del rendimiento pero espero que las operaciones de punto fijo sean más rápidas que las operaciones de coma flotante (en la mayoría de los casos).

Más bien depende de lo que esté haciendo con ellos. Un tipo de punto fijo con el mismo rango que un flotador IEEE sería muchas veces más lento (y muchas veces más grande).

cosas adecuadas para los flotadores: gráficos

3D, la física/ingeniería, simulación, la simulación del clima ....

0

Más bien depende de lo que estés haciendo con ellos. Un tipo de punto fijo con el mismo rango que un flotador IEEE sería muchas veces más lento (y muchas veces más grande).

Bien, pero si quiero una resolución de bits infinitesimalmente pequeña, entonces vuelve a mi punto original: == y! = No tienen sentido en el contexto de un problema así.

Una int me permite expresar ~ 10^9 valores (independientemente del rango) que parece suficiente para cualquier situación en la que me gustaría que dos de ellos sean iguales. Y si eso no es suficiente, use un sistema operativo de 64 bits y obtendrá aproximadamente 10^19 valores distintos.

Puedo expresar valores de un rango de 0 a 10^200 (por ejemplo) en un int, es solo la resolución de bits que sufre (la resolución sería mayor que 1, pero, de nuevo, ninguna aplicación tiene ese tipo de rango, así como ese tipo de resolución).

En resumen, creo que en todos los casos uno representa un continuo de valores, en cuyo caso! = Y == son irrelevantes, o uno representa un conjunto fijo de valores, que pueden correlacionarse con un int (u otro tipo de precisión fija).

0

Un int me permite expresar ~ 10^9 valores (independientemente de la gama) que parece como suficiente para cualquier situación en la que me gustaría preocupan por dos de ellos iguales. Y si eso no es suficiente, use un sistema operativo de 64 bits y obtendrá aproximadamente 10^19 valores distintos.

De hecho, llegué a ese límite ... Estaba tratando de hacer malabares con los tiempos en ps y el tiempo en ciclos de reloj en una simulación en la que tocas fácilmente 10^10 ciclos. No importa lo que hice, muy rápidamente rebasé el pequeño rango de enteros de 64 bits ... 10^19 no es tanto como crees, ¡dame 128 bits de computación ahora!

Floats me permitió obtener una solución a los problemas matemáticos, ya que los valores se desbordaron con ceros de lotes en el extremo inferior. Así que básicamente tenía un punto decimal flotante aronud en el número sin pérdida de precisión (me gustaría con el número más limitado de valores permitidos en la mantisa de un flotador en comparación con un int de 64 bits, ¡pero necesitaba desesperadamente este rango!)

Y entonces las cosas se vuelven a convertir en números enteros para comparar etc.

molesto, y al final he desechado todo el intento y simplemente confiar en los flotadores y < y> para hacer el trabajo. No es perfecto, pero funciona para el caso de uso previsto.

1

En el software numérico, a menudo quiere probar si dos números de coma flotante son exactamente iguales. LAPACK está lleno de ejemplos para tales casos. Claro, el caso más común es donde desea probar si un número de punto flotante es igual a "Cero", "Uno", "Dos", "Medio". Si alguien está interesado, puedo elegir algunos algoritmos y profundizar más en los detalles.

También en BLAS a menudo quiere comprobar si un número de coma flotante es exactamente Cero o Uno.Por ejemplo, el dgemv rutina puede calcular las operaciones de la forma

  • y = beta * y + alfa * A * x
  • y = beta * y + alfa * A^T * x
  • y = beta * y + alpha * A^H * x

Entonces, si beta es igual a Uno, tiene una "asignación más" y para beta equivale a cero una "asignación simple". Entonces, ciertamente puede reducir el costo computacional si le da a estos casos (comunes) un tratamiento especial.

Claro, podría diseñar las rutinas BLAS de tal forma que pueda evitar comparaciones exactas (por ejemplo, utilizando algunos indicadores). Sin embargo, el LAPACK está lleno de ejemplos donde no es posible.

P.S .:

  • Es cierto que hay muchos casos en los que no desea comprobar si hay "es exactamente igual". Para muchas personas, incluso este podría ser el único caso con el que tengan que lidiar. Todo lo que quiero señalar es que también hay otros casos.

  • Aunque LAPACK está escrito en Fortran, la lógica es la misma si está utilizando otros lenguajes de programación para software numérico.

Cuestiones relacionadas