Es bien sabido que comparar flotadores por igualdad es un poco complicado debido a problemas de redondeo y precisión.
Por ejemplo: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
¿Cuál es la forma recomendada de lidiar con esto en Python?
¿Seguramente hay una función de biblioteca estándar para esto en alguna parte?
python
floating-point
Gordon Wrigley
fuente
fuente
all
,any
,max
,min
son cada uno, básicamente, de una sola línea, y no son simplemente proporcionan en una biblioteca, son incorporado funciones. Entonces, las razones del BDFL no son eso. La única línea de código que la mayoría de la gente escribe es bastante poco sofisticada y, a menudo, no funciona, lo cual es una razón sólida para proporcionar algo mejor. Por supuesto, cualquier módulo que proporcione otras estrategias también debería proporcionar advertencias que describan cuándo son apropiadas y, lo que es más importante, cuándo no lo son. El análisis numérico es difícil, no es una gran desgracia que los diseñadores de idiomas generalmente no intenten herramientas para ayudarlo.Respuestas:
Python 3.5 agrega las funciones
math.isclose
ycmath.isclose
como se describe en PEP 485 .Si está utilizando una versión anterior de Python, la función equivalente se proporciona en la documentación .
rel_tol
es una tolerancia relativa, se multiplica por la mayor de las magnitudes de los dos argumentos; A medida que los valores se hacen más grandes, también lo hace la diferencia permitida entre ellos mientras los consideramos iguales.abs_tol
es una tolerancia absoluta que se aplica tal cual en todos los casos. Si la diferencia es menor que cualquiera de esas tolerancias, los valores se consideran iguales.fuente
a
ob
es unnumpy
array
,numpy.isclose
funciona.rel_tol
es una tolerancia relativa , se multiplica por la mayor de las magnitudes de los dos argumentos; A medida que los valores se hacen más grandes, también lo hace la diferencia permitida entre ellos mientras los consideramos iguales.abs_tol
es una tolerancia absoluta que se aplica tal cual en todos los casos. Si la diferencia es menor que cualquiera de esas tolerancias, los valores se consideran iguales.isclose
función (arriba) no es una implementación completa .isclose
siempre se adhiere al criterio menos conservador. Solo lo menciono porque ese comportamiento es contradictorio para mí. Si especificara dos criterios, siempre esperaría que la tolerancia menor reemplazara a la mayor.¿Algo tan simple como lo siguiente no es lo suficientemente bueno?
fuente
abs(f1-f2) < tol*max(abs(f1),abs(f2))
. Este tipo de tolerancia relativa es la única forma significativa de comparar flotadores en general, ya que generalmente se ven afectados por un error de redondeo en los decimales pequeños.>>> abs(0.04 - 0.03) <= 0.01
rindeFalse
. Yo usoPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
abs(f1 - f2) <= allowed_error
no funciona como se esperaba.Estoy de acuerdo en que la respuesta de Gareth es probablemente la más adecuada como una función / solución ligera.
Pero pensé que sería útil tener en cuenta que si está utilizando NumPy o lo está considerando, hay una función empaquetada para esto.
Sin embargo, un pequeño descargo de responsabilidad: instalar NumPy puede ser una experiencia no trivial dependiendo de su plataforma.
fuente
pip
de Windows.Utilice el
decimal
módulo de Python , que proporciona laDecimal
clase.De los comentarios:
fuente
No conozco nada en la biblioteca estándar de Python (o en otro lugar) que implemente la
AlmostEqual2sComplement
función de Dawson . Si ese es el tipo de comportamiento que desea, deberá implementarlo usted mismo. (En ese caso, en lugar de usar los inteligentes trucos bit a bit de Dawson, probablemente harías mejor en usar pruebas más convencionales de la formaif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
o similares. Para obtener un comportamiento similar a Dawson, podrías decir algo así comoif abs(a-b) <= eps*max(EPS,abs(a),abs(b))
para algunos pequeños arregladosEPS
; esto no es exactamente lo mismo que Dawson, pero es similar en espíritu.fuente
eps1
yeps2
defina una tolerancia relativa y absoluta: está preparado para permitira
yb
diferir aproximadamente eneps1
cuanto a su tamañoeps2
.eps
es una sola tolerancia; está preparado para permitira
yb
diferir en aproximadamente eleps
tamaño de su tamaño, con la condición de que se suponga que cualquier tamaño de tamañoEPS
o menor es de tamañoEPS
. Si considera queEPS
es el valor no nórdico más pequeño de su tipo de punto flotante, esto es muy similar al comparador de Dawson (excepto por un factor de 2 ^ # bits porque Dawson mide la tolerancia en ulps).La sabiduría común de que los números de coma flotante no se pueden comparar para la igualdad es inexacta. Los números de coma flotante no son diferentes de los enteros: si evalúa "a == b", obtendrá verdadero si son números idénticos y falso de lo contrario (entendiendo que dos NaNs no son números idénticos).
El problema real es este: si he hecho algunos cálculos y no estoy seguro de que los dos números que tengo que comparar son exactamente correctos, ¿entonces qué? Este problema es el mismo para el punto flotante que para los enteros. Si evalúa la expresión entera "7/3 * 3", no se comparará igual a "7 * 3/3".
Supongamos que preguntamos "¿Cómo comparo enteros para la igualdad?" en tal situación. No hay una única respuesta; lo que debe hacer depende de la situación específica, en particular qué tipo de errores tiene y qué desea lograr.
Aquí hay algunas opciones posibles.
Si desea obtener un resultado "verdadero" si los números matemáticamente exactos fueran iguales, entonces podría intentar usar las propiedades de los cálculos que realiza para demostrar que obtiene los mismos errores en los dos números. Si eso es factible, y compara dos números que resultan de expresiones que darían números iguales si se calculan exactamente, entonces obtendrá "verdadero" de la comparación. Otro enfoque es que podría analizar las propiedades de los cálculos y demostrar que el error nunca excede una cierta cantidad, tal vez una cantidad absoluta o una cantidad relativa a una de las entradas o una de las salidas. En ese caso, puede preguntar si los dos números calculados difieren en esa cantidad como máximo y devolver "verdadero" si están dentro del intervalo. Si no puede probar un error vinculado, puedes adivinar y esperar lo mejor. Una forma de adivinar es evaluar muchas muestras aleatorias y ver qué tipo de distribución se obtiene en los resultados.
Por supuesto, dado que solo establecemos el requisito de que sea "verdadero" si los resultados matemáticamente exactos son iguales, dejamos abierta la posibilidad de que sea "verdadero" incluso si son desiguales. (De hecho, podemos satisfacer el requisito siempre devolviendo "verdadero". Esto hace que el cálculo sea simple pero generalmente no es deseable, por lo que discutiremos la mejora de la situación a continuación).
Si desea obtener un resultado "falso" si los números matemáticamente exactos fueran desiguales, debe probar que su evaluación de los números arroja números diferentes si los números matemáticamente exactos fueran desiguales. Esto puede ser imposible para fines prácticos en muchas situaciones comunes. Así que consideremos una alternativa.
Un requisito útil podría ser que obtengamos un resultado "falso" si los números matemáticamente exactos difieren en más de una cierta cantidad. Por ejemplo, tal vez vamos a calcular dónde viajó una pelota lanzada en un juego de computadora, y queremos saber si golpeó un bate. En este caso, ciertamente queremos ser "verdaderos" si la pelota golpea el bate, y queremos ser "falsos" si la pelota está lejos del bate, y podemos aceptar una respuesta "verdadera" incorrecta si la pelota entra una simulación matemáticamente exacta falló el bate pero está a un milímetro de golpear el bate. En ese caso, debemos demostrar (o adivinar / estimar) que nuestro cálculo de la posición de la pelota y la posición del bate tienen un error combinado de como máximo un milímetro (para todas las posiciones de interés). Esto nos permitiría volver siempre "
Entonces, cómo decide qué devolver cuando compara números de punto flotante depende mucho de su situación específica.
En cuanto a cómo probar los límites de error para los cálculos, eso puede ser un tema complicado. Cualquier implementación de punto flotante que use el estándar IEEE 754 en el modo redondeado al más cercano devuelve el número de punto flotante más cercano al resultado exacto para cualquier operación básica (en particular, multiplicación, división, suma, resta, raíz cuadrada). (En caso de empate, redondee para que el bit bajo sea par.) (Tenga especial cuidado con la raíz cuadrada y la división; su implementación de lenguaje podría usar métodos que no se ajustan a IEEE 754 para esos). Debido a este requisito, sabemos que El error en un solo resultado es como máximo la mitad del valor del bit menos significativo. (Si fuera más, el redondeo habría ido a un número diferente que está dentro de la mitad del valor).
Continuar desde allí se vuelve mucho más complicado; El siguiente paso es realizar una operación donde una de las entradas ya tiene algún error. Para expresiones simples, estos errores pueden seguirse a través de los cálculos para alcanzar un límite en el error final. En la práctica, esto solo se hace en algunas situaciones, como trabajar en una biblioteca matemática de alta calidad. Y, por supuesto, necesita un control preciso sobre exactamente qué operaciones se realizan. Los lenguajes de alto nivel a menudo le dan al compilador mucha holgura, por lo que es posible que no sepa en qué orden se realizan las operaciones.
Hay mucho más que podría (y está) escrito sobre este tema, pero tengo que parar allí. En resumen, la respuesta es: no hay una rutina de biblioteca para esta comparación porque no hay una solución única que se adapte a la mayoría de las necesidades que valga la pena poner en una rutina de biblioteca. (Si comparar con un intervalo de error relativo o absoluto es suficiente para usted, puede hacerlo simplemente sin una rutina de biblioteca).
fuente
(7/3*3 == 7*3/3)
. ImpresoFalse
.from __future__ import division
. Si no hace eso, no hay números de coma flotante y la comparación es entre dos enteros.Si desea usarlo en pruebas / contexto TDD, diría que esta es una forma estándar:
fuente
math.isclose () se ha agregado a Python 3.5 para eso ( código fuente ). Aquí hay un puerto para Python 2. Su diferencia con respecto a Mark Ransom es que puede manejar "inf" y "-inf" correctamente.
fuente
La siguiente comparación me pareció útil:
fuente
str(.1 + .2) == str(.3)
devuelve False. El método descrito anteriormente solo funciona para Python 2.Para algunos de los casos en los que puede afectar la representación del número fuente, puede representarlos como fracciones en lugar de flotantes, utilizando un numerador y un denominador enteros. De esa manera puedes tener comparaciones exactas.
Vea la fracción del módulo de fracciones para más detalles.
fuente
Me gustó la sugerencia de @Sesquipedal pero con modificaciones (un caso de uso especial cuando ambos valores son 0 devuelve False). En mi caso, estaba en Python 2.7 y solo usé una función simple:
fuente
Útil para el caso en el que desea asegurarse de que 2 números sean iguales 'hasta la precisión', no es necesario especificar la tolerancia:
Encuentra la precisión mínima de los 2 números
Redondea ambos con una precisión mínima y compara
Tal como está escrito, solo funciona para números sin la 'e' en su representación de cadena (es decir, 0.9999999999995e-4 <número <= 0.9999999999995e11)
Ejemplo:
fuente
isclose(1.0, 1.1)
produceFalse
yisclose(0.1, 0.000000000001)
regresaTrue
.Para comparar hasta un decimal dado sin
atol/rtol
:fuente
Esto quizás sea un truco un poco feo, pero funciona bastante bien cuando no necesita más que la precisión de flotación predeterminada (aproximadamente 11 decimales).
La función round_to usa el método de formato de la clase str incorporada para redondear el flotante a una cadena que representa el flotante con el número de decimales necesarios, y luego aplica la función incorporada eval a la cadena flotante redondeada para volver al tipo numérico flotante.
La función is_close solo aplica un condicional simple al flotante redondeado.
Actualizar:
Según lo sugerido por @stepehjfox, una forma más limpia de construir una función rount_to evitando "eval" es usar el formato anidado :
Siguiendo la misma idea, el código puede ser aún más simple usando las nuevas y geniales cadenas f (Python 3.6+):
Entonces, incluso podríamos envolverlo todo en una función simple y limpia 'is_close' :
fuente
eval()
para obtener un formato parametrizado. Algo asíreturn '{:.{precision}f'.format(float_num, precision=decimal_precision)
debería hacerloreturn '{:.{precision}}f'.format(float_num, precision=decimal_precision)
En términos de error absoluto, solo puede verificar
Alguna información de por qué flotador actúa raro en Python https://youtu.be/v4HhvoNLILk?t=1129
También puede usar math.isclose para errores relativos
fuente