¿Cuál sería la forma más eficiente de comparar dos double
o dos float
valores?
Simplemente hacer esto no es correcto:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Pero algo como:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Parece que el procesamiento de residuos.
¿Alguien conoce un comparador de flotador más inteligente?
<invoke Knuth>
La optimización prematura es la fuente de todos los males.</invoke Knuth>
Simplemente vaya con abs (ab) <EPS como se señaló anteriormente, es claro y fácil de entender.==
puede ser perfectamente correcto, pero esto depende completamente del contexto no dado en la pregunta. Hasta que se conozca ese contexto,==
sigue siendo la "forma más eficiente" .Respuestas:
Tenga mucho cuidado con cualquiera de las otras sugerencias. Todo depende del contexto.
He pasado mucho tiempo rastreando errores en un sistema que suponía que
a==b
sí|a-b|<epsilon
. Los problemas subyacentes fueron:La presunción implícita en un algoritmo que si
a==b
yb==c
luegoa==c
.Usando el mismo épsilon para líneas medidas en pulgadas y líneas medidas en milésimas de pulgada (.001 pulgadas). Eso es
a==b
pero1000a!=1000b
. (Esta es la razón por la cual AlmostEqual2sComplement solicita el epsilon o max ULPS).¡El uso del mismo épsilon tanto para el coseno de los ángulos como para la longitud de las líneas!
Usando tal función de comparación para ordenar elementos en una colección. (En este caso, el uso del operador C ++ incorporado == para dobles produjo resultados correctos).
Como dije: todo depende del contexto y del tamaño esperado de
a
yb
.Por cierto,
std::numeric_limits<double>::epsilon()
es la "máquina épsilon". Es la diferencia entre 1.0 y el siguiente valor representable por un doble. Supongo que podría usarse en la función de comparación, pero solo si los valores esperados son inferiores a 1. (Esto es en respuesta a la respuesta de @ cdv ...)Además, si básicamente tiene
int
aritmética endoubles
(aquí usamos dobles para mantener valores int en ciertos casos) su aritmética será correcta. Por ejemplo, 4.0 / 2.0 será lo mismo que 1.0 + 1.0. Esto es siempre que no haga cosas que generen fracciones (4.0 / 3.0) o que no salgan del tamaño de un int.fuente
fabs(a)+fabs(b)
pero con compensación para NaN, suma 0 y desbordamiento, esto se vuelve bastante complejo.float
/double
es MANTISSA x 2 ^ EXP .epsilon
dependerá del exponente. Por ejemplo, si la mantisa es de 24 bits y el exponente está firmado con 8 bits, entonces1/(2^24)*2^127
o~2^103
esepsilon
para algunos valores; o esto se refiere a un mínimo de épsilon ?|a-b|<epsilon
, no es correcto. Agregue este enlace a su respuesta; si está de acuerdo cygnus-software.com/papers/comparingfloats/comparingfloats.htm y puedo eliminar mis comentarios tontos.La comparación con un valor épsilon es lo que hace la mayoría de las personas (incluso en la programación de juegos).
Sin embargo, debe cambiar su implementación un poco:
Editar: Christer ha agregado una gran cantidad de información excelente sobre este tema en una publicación de blog reciente . Disfrutar.
fuente
float a = 3.4; if(a == 3.4){...}
es decir, cuando se compara un punto flotante almacenado con un literal | En este caso, ambos números se almacenan, por lo que tendrán la misma representación, si es igual, entonces, ¿cuál es el daño al hacera == b
?EPSILON
se define comoDBL_EPSILON
. Normalmente será un valor específico elegido dependiendo de la precisión requerida de la comparación.EPSILON
la comparación no funciona cuando los flotadores son grandes, ya que la diferencia entre flotadores consecutivos también se hace grande. Ver este artículo .EPSILON
es bastante inútil. Debe comparar con un umbral que tenga sentido para las unidades disponibles. Además, usestd::abs
ya que está sobrecargado para diferentes tipos de punto flotante.Descubrí que Google C ++ Testing Framework contiene una buena implementación multiplataforma basada en plantillas de AlmostEqual2sComplement que funciona tanto en dobles como en flotantes. Dado que se publica bajo la licencia BSD, usarlo en su propio código no debería ser un problema, siempre que conserve la licencia. Extraje el siguiente código de
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h y agregó la licencia en la parte superior.Asegúrese de #definir GTEST_OS_WINDOWS a algún valor (o cambiar el código donde está acostumbrado a algo que se ajuste a su base de código; después de todo, tiene licencia BSD).
Ejemplo de uso:
Aquí está el código:
EDITAR: Esta publicación tiene 4 años. Probablemente todavía sea válido, y el código es bueno, pero algunas personas encontraron mejoras. Lo mejor es obtener la última versión
AlmostEquals
correcta del código fuente de Google Test, y no la que pegué aquí.fuente
La comparación de números de coma flotante depende del contexto. Dado que incluso cambiar el orden de las operaciones puede producir resultados diferentes, es importante saber qué tan "igual" quiere que sean los números.
La comparación de números de coma flotante por Bruce Dawson es un buen lugar para comenzar cuando se busca la comparación de coma flotante.
Las siguientes definiciones son de El arte de la programación de computadoras de Knuth :
Por supuesto, elegir épsilon depende del contexto y determina qué tan igual quiere que sean los números.
Otro método para comparar números de coma flotante es mirar el ULP (unidades en último lugar) de los números. Si bien no se trata específicamente de comparaciones, el documento Lo que todo informático debe saber sobre los números de coma flotante es un buen recurso para comprender cómo funciona la coma flotante y cuáles son las trampas, incluido lo que es ULP.
fuente
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
me salvó la vida LOL Tenga en cuenta que esta versión (no he comprobado si se aplica a las demás también) también considera el cambio que podría ocurrir en la parte integral del número de coma flotante (ejemplo:2147352577.9999997616 == 2147352576.0000000000
donde puede ver claramente que hay casi una diferencia de2
entre los dos números) que es bastante bueno! Esto sucede cuando el error de redondeo acumulado desborda la parte decimal del número.std::max(std::abs(a), std::abs(b))
(o constd::min()
);std::abs
en C ++ está sobrecargado con tipos flotantes y dobles, por lo que funciona bien (fabs
aunque siempre puede mantenerse legible).Para un enfoque más profundo, lea Comparar números de punto flotante . Aquí está el fragmento de código de ese enlace:
fuente
*(int*)&A;
" la estricta regla de alias?Al darse cuenta de que este es un hilo viejo, pero este artículo es uno de los más sencillos que he encontrado al comparar números de coma flotante y si desea explorar más, también tiene referencias más detalladas y el sitio principal cubre una gama completa de problemas tratar con números de coma flotante La guía de coma flotante: comparación .
Podemos encontrar un artículo algo más práctico en Tolerancias de punto flotante revisado y señala que hay una prueba de tolerancia absoluta , que se reduce a esto en C ++:
y prueba de tolerancia relativa :
El artículo señala que la prueba absoluta falla cuando
x
yy
son grandes y falla en el caso relativo cuando son pequeños. Suponiendo que la tolerancia absoluta y relativa es la misma, una prueba combinada se vería así:fuente
La forma portátil de obtener epsilon en C ++ es
Entonces la función de comparación se convierte en
fuente
Terminé pasando bastante tiempo revisando material en este gran hilo. Dudo que todos quieran pasar tanto tiempo, así que destacaría el resumen de lo que aprendí y la solución que implementé.
Sumario rápido
numeric_limits::epsilon()
la misma que FLT_EPSILON en float.h. Sin embargo, esto es problemático porque epsilon para comparar valores como 1.0 no es lo mismo que epsilon para valores como 1E9. El FLT_EPSILON se define para 1.0.fabs(a-b) <= epsilon
Sin embargo, la esto no funciona porque el epsilon predeterminado está definido para 1.0. Necesitamos escalar epsilon hacia arriba o hacia abajo en términos de ay b.max(a,b)
o puede obtener los siguientes números representables alrededor de ay luego ver si b cae dentro de ese rango. El primero se llama método "relativo" y luego se llama método ULP.Implementación de funciones de utilidad (C ++ 11)
fuente
isDefinitelyLessThan
controlesdiff < tolerance
, lo que significa que ayb son casi iguales (y, por lo tanto, a no es definitivamente menor que b). ¿No tiene más sentido comprobar la diferencia de tolerancia en ambos casos? O quizás agregue unorEqualTo
argumento que controle si la verificación de igualdad aproximada debe devolver verdadero o no.El código que escribiste tiene errores:
El código correcto sería:
(... y sí, esto es diferente)
Me pregunto si los fabs no te harían perder la evaluación perezosa en algún caso. Yo diría que depende del compilador. Es posible que desee probar ambos. Si son equivalentes en promedio, tome la implementación con fabs.
Si tiene alguna información sobre cuál de los dos flotantes es más probable que sea más grande que otro, puede jugar en el orden de la comparación para aprovechar mejor la evaluación perezosa.
Finalmente, podría obtener mejores resultados al incluir esta función. Sin embargo, no es probable que mejore mucho ...
Editar: DO, gracias por corregir su código. Borré mi comentario en consecuencia
fuente
Esto está bien si:
Pero de lo contrario te llevará a problemas. Los números de doble precisión tienen una resolución de aproximadamente 16 decimales. Si los dos números que está comparando son de mayor magnitud que EPSILON * 1.0E16, entonces también podría estar diciendo:
Examinaré un enfoque diferente que asume que debe preocuparse por el primer problema y supondrá que el segundo está bien para su aplicación. Una solución sería algo como:
Esto es costoso computacionalmente, pero a veces es lo que se requiere. Esto es lo que tenemos que hacer en mi empresa porque tratamos con una biblioteca de ingeniería y las entradas pueden variar en unas pocas docenas de órdenes de magnitud.
De todos modos, el punto es este (y se aplica a prácticamente todos los problemas de programación): evalúe cuáles son sus necesidades, luego encuentre una solución para satisfacer sus necesidades: no asuma que la respuesta fácil abordará sus necesidades. Si después de su evaluación encuentra que eso
fabs(a-b) < EPSILON
será suficiente, perfecto, ¡úselo! Pero tenga en cuenta sus deficiencias y otras posibles soluciones también.fuente
Como otros han señalado, usar un épsilon de exponente fijo (como 0.0000001) será inútil para valores alejados del valor de épsilon. Por ejemplo, si sus dos valores son 10000.000977 y 10000, entonces NO hay valores de punto flotante de 32 bits entre estos dos números: 10000 y 10000.000977 están lo más cerca posible que pueda ser sin ser idéntico bit por bit. Aquí, un épsilon de menos de 0,0009 no tiene sentido; también podrías usar el operador de igualdad directa.
Del mismo modo, a medida que los dos valores se aproximan a epsilon en tamaño, el error relativo crece al 100%.
Por lo tanto, tratar de mezclar un número de punto fijo como 0.00001 con valores de punto flotante (donde el exponente es arbitrario) es un ejercicio sin sentido. Esto solo funcionará si puede estar seguro de que los valores de operando se encuentran dentro de un dominio estrecho (es decir, cerca de algún exponente específico), y si selecciona correctamente un valor de épsilon para esa prueba específica. Si saca un número del aire ("¡Hey! 0.00001 es pequeño, ¡eso debe ser bueno!"), Está condenado a errores numéricos. He pasado mucho tiempo depurando códigos numéricos incorrectos donde algunos idiotas pobres arrojan valores aleatorios de epsilon para hacer que otro caso de prueba funcione.
Si hace programación numérica de cualquier tipo y cree que necesita alcanzar épsilons de punto fijo, LEA EL ARTÍCULO DE BRUCE SOBRE COMPARAR NÚMEROS DE PUNTO FLOTANTE .
Comparación de números de coma flotante
fuente
Qt implementa dos funciones, quizás puedas aprender de ellas:
Y es posible que necesite las siguientes funciones, ya que
fuente
La comparación de propósito general de los números de coma flotante generalmente no tiene sentido. Cómo comparar realmente depende de un problema en cuestión. En muchos problemas, los números están suficientemente discretos para permitir compararlos dentro de una tolerancia dada. Desafortunadamente, hay tantos problemas, donde tal truco realmente no funciona. Por ejemplo, considere trabajar con una función Heaviside (paso) de un número en cuestión (me vienen a la mente las opciones de acciones digitales) cuando sus observaciones están muy cerca de la barrera. Realizar una comparación basada en la tolerancia no serviría de mucho, ya que efectivamente cambiaría el problema de la barrera original a dos nuevas. Nuevamente, no existe una solución de propósito general para tales problemas y la solución particular podría requerir ir tan lejos como cambiar el método numérico para lograr la estabilidad.
fuente
Desafortunadamente, incluso su código "derrochador" es incorrecto. EPSILON es el valor más pequeño que se podría agregar a 1.0 y cambiar su valor. El valor 1.0 es muy importante: los números más grandes no cambian cuando se agregan a EPSILON. Ahora, puede escalar este valor a los números que está comparando para saber si son diferentes o no. La expresión correcta para comparar dos dobles es:
Esto es mínimo. Sin embargo, en general, desearía tener en cuenta el ruido en sus cálculos e ignorar algunos de los bits menos significativos, por lo que se vería una comparación más realista:
Si el rendimiento de comparación es muy importante para usted y conoce el rango de sus valores, entonces debería usar números de punto fijo.
fuente
EPSILON
en la pregunta esDBL_EPSILON
oFLT_EPSILON
? El problema está en su propia imaginación, donde sustituyóDBL_EPSILON
(que de hecho sería la elección incorrecta) en un código que no lo usó.Mi clase basada en respuestas publicadas previamente. Muy similar al código de Google, pero utilizo un sesgo que empuja todos los valores de NaN por encima de 0xFF000000. Eso permite una verificación más rápida de NaN.
Este código está destinado a demostrar el concepto, no ser una solución general. El código de Google ya muestra cómo calcular todos los valores específicos de la plataforma y no quería duplicar todo eso. He realizado pruebas limitadas en este código.
fuente
Aquí está la prueba de que el uso
std::numeric_limits::epsilon()
no es la respuesta: falla para valores mayores que uno:Prueba de mi comentario anterior:
Correr produce esta salida:
Tenga en cuenta que en el segundo caso (uno y solo mayor que uno), los dos valores de entrada están lo más cerca posible, y aún se comparan como no cercanos. Por lo tanto, para valores superiores a 1.0, también podría usar una prueba de igualdad. Los épsilons fijos no lo salvarán al comparar valores de punto flotante.
fuente
return *(reinterpret_cast<double*>(&x));
aunque generalmente funciona, es un comportamiento indefinido.numeric_limits<>::epsilon
y el punto de piso IEEE 754.Encontré otra implementación interesante en: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
fuente
Sería muy cuidadoso con cualquiera de estas respuestas que impliquen la resta de punto flotante (por ejemplo, fabs (ab) <epsilon). Primero, los números de coma flotante se vuelven más dispersos a mayores magnitudes y a magnitudes suficientemente altas donde el espacio es mayor que épsilon, también podría estar haciendo a == b. En segundo lugar, restar dos números de punto flotante muy cercanos (ya que estos tienden a ser, dado que está buscando una igualdad cercana) es exactamente cómo se obtiene la cancelación catastrófica .
Si bien no es portátil, creo que la respuesta de grom hace el mejor trabajo para evitar estos problemas.
fuente
a
yb
ellos mismos. No hay absolutamente ningún problema con el uso de resta en coma flotante como parte de una comparación fuzzy (aunque como han dicho otros, un valor de epsilon absoluta puede o puede no ser apropiado para un caso de uso dado.)En realidad, hay casos en el software numérico en los que desea verificar si dos números de coma flotante son exactamente iguales. Publiqué esto en una pregunta similar
https://stackoverflow.com/a/10973098/1447411
Por lo tanto, no puede decir que "CompareDoubles1" está mal en general.
fuente
Depende de cuán preciso desee que sea la comparación. Si desea comparar exactamente el mismo número, simplemente vaya con ==. (Casi nunca quieres hacer esto a menos que realmente quieras exactamente el mismo número). En cualquier plataforma decente también puedes hacer lo siguiente:
ya que
fabs
tiende a ser bastante rápido. Por bastante rápido me refiero a que es básicamente un Y bit a bit, así que es mejor que sea rápido.Y los trucos de números enteros para comparar dobles y flotantes son buenos, pero tienden a dificultar el manejo efectivo de las diferentes tuberías de CPU. Y definitivamente no es más rápido en ciertas arquitecturas en orden en estos días debido al uso de la pila como un área de almacenamiento temporal para valores que se usan con frecuencia. (Load-hit-store para quienes se preocupan).
fuente
En términos de la escala de cantidades:
Si
epsilon
la pequeña fracción de la magnitud de la cantidad (es decir, el valor relativo) en cierto sentido físicoA
yB
tipos es comparable en el mismo sentido, creo que lo siguiente es bastante correcto:fuente
Yo uso este código:
fuente
epsilon
es para lo que sirve.epsilon
es simplemente la distancia entre 1 y el siguiente número representable después de 1. En el mejor de los casos, ese código solo está tratando de verificar si los dos números son exactamente iguales entre sí, pero debido a que los no poderes de 2 se multiplican porepsilon
, Ni siquiera está haciendo eso correctamente.std::fabs(std::min(v1, v2))
es incorrecto: para las entradas negativas, elige la que tiene la mayor magnitud.Escribo esto para Java, pero tal vez lo encuentres útil. Utiliza largos en lugar de dobles, pero se ocupa de NaNs, subnormales, etc.
Tenga en cuenta que después de varias operaciones de punto flotante, el número puede ser muy diferente de lo que esperamos. No hay código para arreglar eso.
fuente
¿Qué tal esto?
He visto varios enfoques, pero nunca he visto esto, ¡así que tengo curiosidad por escuchar cualquier comentario también!
fuente
Utilicé esta función para mi pequeño proyecto y funciona, pero tenga en cuenta lo siguiente:
El error de doble precisión puede crear una sorpresa para ti. Digamos que épsilon = 1.0e-6, entonces 1.0 y 1.000001 NO deben considerarse iguales de acuerdo con el código anterior, pero en mi máquina la función los considera iguales, esto se debe a que 1.000001 no se puede traducir con precisión a un formato binario, probablemente sea 1.0000009xxx. Lo pruebo con 1.0 y 1.0000011 y esta vez obtengo el resultado esperado.
fuente
Esta es otra solución con lambda:
fuente
Mi camino puede no ser correcto pero útil
Convierta ambos flotantes en cadenas y luego compare cadenas
la superposición del operador también se puede hacer
fuente
No puedes comparar dos
double
con un fijoEPSILON
. Dependiendo del valor dedouble
,EPSILON
varía.Una mejor doble comparación sería:
fuente
De una manera más genérica:
fuente
a
yb
ya son más pequeños que laepsilon()
diferencia aún pueden ser significativos. Por el contrario, si los números son muy grandes, incluso un par de bits de error harán que la comparación falle incluso si desea que los números se consideren iguales. Esta respuesta es exactamente el tipo de algoritmo de comparación "genérico" que desea evitar.¿Por qué no realizar bitor XOR? Dos números de coma flotante son iguales si sus bits correspondientes son iguales. Creo que la decisión de colocar los bits de exponente antes de la mantisa se hizo para acelerar la comparación de dos flotadores. Creo que muchas respuestas aquí están perdiendo el punto de comparación epsilon. El valor de Epsilon solo depende de con qué precisión se comparan los números de coma flotante. Por ejemplo, después de hacer algo de aritmética con flotantes, obtienes dos números: 2.5642943554342 y 2.5642943554345. No son iguales, pero para la solución solo importan 3 dígitos decimales, por lo que son iguales: 2.564 y 2.564. En este caso, elige epsilon igual a 0.001. La comparación de Epsilon también es posible con XOR bit a bit. Corrígeme si estoy equivocado.
fuente