¿Cuál es la mejor manera de comparar flotadores para casi la igualdad en Python?

Es bien sabido que la comparación de flotadores para la igualdad es un poco complicada 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 algún lugar?

Python 3.5 agrega las funciones math.isclose y cmath.isclose como se describe en PEP 485 .

Si está utilizando una versión anterior de Python, la función equivalente se encuentra en la documentación .

 def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): return abs(ab) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) 

rel_tol es una tolerancia relativa, se multiplica por la mayor de las magnitudes de los dos argumentos; a medida que los valores aumentan, también lo hace la diferencia permitida entre ellos y al mismo tiempo los considera iguales.

abs_tol es una tolerancia absoluta que se aplica como está en todos los casos. Si la diferencia es menor que cualquiera de esas tolerancias, los valores se consideran iguales.

¿Es algo tan simple como lo siguiente no es suficiente?

 return abs(f1 - f2) <= allowed_error 

Estoy de acuerdo en que la respuesta de Gareth es probablemente la más apropiada como una solución / función liviana.

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.

 numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False) 

Sin embargo, un poco de exención de responsabilidad: la instalación de NumPy puede ser una experiencia no trivial en función de su plataforma.

Usa el módulo decimal de Python, que proporciona la clase Decimal .

De los comentarios:

Vale la pena señalar que si está haciendo un trabajo pesado en matemáticas y no necesita absolutamente la precisión del decimal, esto realmente puede atascar las cosas. Los flotadores son mucho, más rápidos de tratar, pero imprecisos. Los decimales son extremadamente precisos pero lentos.

No tengo conocimiento de nada en la biblioteca estándar de Python (ni en ninguna otra parte) que implemente la función AlmostEqual2sComplement de Dawson. Si ese es el tipo de comportamiento que desea, deberá implementarlo usted mismo. (En cuyo caso, en lugar de usar los hacks inteligentes a nivel de bits de Dawson, probablemente sería mejor usar pruebas más convencionales de la forma if abs(ab) <= eps1*(abs(a)+abs(b)) + eps2 o similar . Para obtener un comportamiento similar al de Dawson, puede decir algo como if abs(ab) <= eps*max(EPS,abs(a),abs(b)) para algunos EPS fijos pequeños; esto no es exactamente lo mismo que Dawson , pero es similar en espíritu.

La sabiduría común de que los números de punto flotante no se pueden comparar para la igualdad es inexacta. Los números de punto flotante no son diferentes de los enteros: si evalúa “a == b”, obtendrá verdadero si son números idénticos y falso de otro modo (entendiendo que dos NaN no son números idénticos, por supuesto).

El problema real es el siguiente: si he hecho algunos cálculos y no estoy seguro de que los dos números que tengo que comparar sean 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”.

Entonces, supongamos que preguntamos “¿Cómo comparo los 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é quiere lograr.

Aquí hay algunas opciones posibles.

Si desea obtener un resultado “verdadero” si los números matemáticamente exactos serían iguales, entonces podría intentar usar las propiedades de los cálculos que realice 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 calcularan exactamente, entonces obtendrás “verdadero” de la comparación. Otro enfoque es que podría analizar las propiedades de los cálculos y probar que el error nunca supera 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 como máximo en esa cantidad y devolver “verdadero” si están dentro del intervalo. Si no puede probar un límite de error, puede adivinar y esperar lo mejor. Una forma de adivinar es evaluar muchas muestras aleatorias y ver qué tipo de distribución obtiene en los resultados.

Por supuesto, ya que solo establecemos el requisito de que obtenga “verdadero” si los resultados matemáticamente exactos son iguales, dejamos abierta la posibilidad de que obtenga “verdadero” incluso si son desiguales. (De hecho, podemos satisfacer el requisito devolviendo siempre “verdadero”. Esto hace que el cálculo sea simple pero generalmente no es deseable, así que discutiré cómo mejorar la situación a continuación).

Si desea obtener un resultado “falso” si los números matemáticamente exactos serían desiguales, debe demostrar que su evaluación de los números produce números diferentes si los números matemáticamente exactos serían desiguales. Esto puede ser imposible para propósitos 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 obtener “verdadero” si la pelota golpea al bate, y queremos obtener “falso” si el balón está lejos del bate, y podemos aceptar una respuesta “verdadera” incorrecta si el balón está una simulación matemáticamente exacta falló el bate pero está a un milímetro de golpear el bate. En ese caso, debemos probar (o adivinar / estimar) que nuestro cálculo de la posición de la bola y la posición del bate tiene un error combinado de como máximo un milímetro (para todas las posiciones de interés). Esto nos permitiría devolver siempre “falso” si la pelota y el bate están separados por más de un milímetro, devolver “verdadero” si se tocan y devolver “verdadero” si están lo suficientemente cerca como para ser aceptables.

Entonces, la forma en que decida qué devolver al comparar números de punto flotante depende en gran medida de su situación específica.

En cuanto a la forma en que comprobamos los límites de error para los cálculos, puede ser un tema complicado. Cualquier implementación de punto flotante que use el estándar IEEE 754 en el modo de redondeo más cercano devuelve el número de punto flotante más cercano al resultado exacto para cualquier operación básica (notablemente multiplicación, división, sum, resta, raíz cuadrada). (En caso de empate, redondee para que el bit bajo sea parejo). (Tenga especial cuidado con la raíz cuadrada y la división; su implementación de lenguaje podría usar métodos que no cumplen con IEEE 754 para ellos). Debido a este requisito, conocemos el 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).

Pasar de allí se vuelve mucho más complicado; el siguiente paso es realizar una operación en la que 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 de matemáticas 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 comstackdor 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 detenerme 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 vale la pena incluir en una rutina de biblioteca. (Si la comparación con un intervalo de error relativo o absoluto es suficiente para usted, puede hacerlo simplemente sin una rutina de biblioteca).

Si quieres usarlo en el contexto de prueba / TDD, diría que esta es una forma estándar:

 from nose.tools import assert_almost_equals assert_almost_equals(x, y, places=7) #default is 7 

Se ha agregado math.isclose () a Python 3.5 para eso ( código fuente ). Aquí hay un puerto de Python 2. La diferencia con una línea de Mark Ransom es que puede manejar “inf” e “-inf” correctamente.

 def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): ''' Python 2 implementation of Python 3.5 math.isclose() https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993 ''' # sanity check on the inputs if rel_tol < 0 or abs_tol < 0: raise ValueError("tolerances must be non-negative") # short circuit exact equality -- needed to catch two infinities of # the same sign. And perhaps speeds things up a bit sometimes. if a == b: return True # This catches the case of two infinities of opposite sign, or # one infinity and one finite number. Two infinities of opposite # sign would otherwise have an infinite relative tolerance. # Two infinities of the same sign are caught by the equality check # above. if math.isinf(a) or math.isinf(b): return False # now do the regular computation # this is essentially the "weak" test from the Boost library diff = math.fabs(b - a) result = (((diff <= math.fabs(rel_tol * b)) or (diff <= math.fabs(rel_tol * a))) or (diff <= abs_tol)) return result 

Encontré la siguiente comparación útil:

 str(f1) == str(f2) 

Para algunos de los casos en los que puede afectar la representación del número de origen, puede representarlos como fracciones en lugar de flotantes, utilizando un numerador entero y un denominador. De esa manera usted puede tener comparaciones exactas.

Ver el módulo de fracciones de fracciones para más detalles.

Me gustó la sugerencia de @Sesquipedal pero con la modificación (un caso de uso especial cuando ambos valores son 0 devuelve Falso). En mi caso, estaba en Python 2.7 y solo usé una función simple:

 if f1 ==0 and f2 == 0: return True else: return abs(f1-f2) < tol*max(abs(f1),abs(f2)) 

Útil para el caso en el que desee asegurarse de que 2 números sean iguales ‘de precisión’, sin necesidad de especificar la tolerancia:

  • Encuentra la precisión mínima de los 2 números.

  • Redondea ambos a la mínima precisión y compara

 def isclose(a,b): astr=str(a) aprec=len(astr.split('.')[1]) if '.' in astr else 0 bstr=str(b) bprec=len(bstr.split('.')[1]) if '.' in bstr else 0 prec=min(aprec,bprec) return round(a,prec)==round(b,prec) 

Tal como está escrito, solo funciona con números sin la ‘e’ en su representación de cadena (lo que significa 0.9999999999995e-4

Ejemplo:

 >>> isclose(10.0,10.049) True >>> isclose(10.0,10.05) False 

Tal vez sea un truco un poco feo, pero funciona bastante bien cuando no necesitas más que la precisión de flotación predeterminada (alrededor de 11 decimales). Funciona bien en python 2.7.

La función round_to utiliza 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 regresar al tipo numérico flotante.

La función is_close solo aplica un condicional simple al flotador redondeado.

 def round_to(float_num, decimal_precision): return eval("'{:." + str(int(decimal_precision)) + "f}'.format(" + str(float_num) + ")") def is_close(float_a, float_b, decimal_precision): if round_to(float_a, decimal_precision) == round_to(float_b, decimal_precision): return True return False a = 10.0 / 3 # Result: 3.3333333333333335 b = 10.0001 / 3 # Result: 3.3333666666666666 print is_close(a, b, decimal_precision=4) # Result: False print is_close(a, b, decimal_precision=3) # Result: True 

Para comparar hasta un decimal dado sin atol/rtol :

 def almost_equal(a, b, decimal=6): return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal) print(almost_equal(0.0, 0.0001, decimal=5)) # False print(almost_equal(0.0, 0.0001, decimal=4)) # True