2010-11-15 20 views
16

Tengo una gran cantidad de código python que intenta manejar números con 4 precisión decimal y estoy atrapado con python 2.4 por muchas razones. El código hace matemáticas muy simplistas (es un código de administración de crédito que toma o agrega créditos en su mayoría)Mal en el decimal/flotante python

Tiene uso entremezclado de float y Decimal (MySQLdb devuelve objetos decimales para tipos SQL DECIMAL). Después de varios errores extraños que surgen del uso, he encontrado que la causa raíz de todos son unos pocos lugares en el código que flotan y se están comparando los decimales.

llegué a casos como éste:

>>> from decimal import Decimal 
>>> max(Decimal('0.06'), 0.6) 
Decimal("0.06") 

Ahora mi miedo es que yo no podría ser capaz de atrapar todos los casos en el código. (un programador normal seguirá haciendo x> 0 en lugar de x> Decimal ('0.0000') y es muy difícil de evitar)

He creado un parche (inspirado en las mejoras del paquete decimal en Python 2.7) .

import decimal 
def _convert_other(other): 
    """Convert other to Decimal. 

    Verifies that it's ok to use in an implicit construction. 
    """ 
    if isinstance(other, Decimal): 
     return other 
    if isinstance(other, (int, long)): 
     return Decimal(other) 
    # Our small patch begins 
    if isinstance(other, float): 
     return Decimal(str(other)) 
    # Our small patch ends 
    return NotImplemented 
decimal._convert_other = _convert_other 

yo sólo lo hacen en una biblioteca de carga muy temprano y que va a cambiar el comportamiento del paquete decimal al permitir la flotación a la conversión decimal antes de comparaciones (para evitar golpear objeto por defecto de Python para oponerse comparación).

Utilicé específicamente "str" ​​en lugar de "repr", ya que corrige algunas de las cajas de redondeo del flotador. P.ej.

>>> Decimal(str(0.6)) 
Decimal("0.6") 
>>> Decimal(repr(0.6)) 
Decimal("0.59999999999999998") 

Ahora mi pregunta es: Me estoy perdiendo algo aquí? ¿Es esto bastante seguro? o estoy rompiendo algo aquí? (Estoy pensando que los autores del paquete tenían razones muy importantes para evitar flotadores tanto)

Respuesta

4

Creo que quieres raise NotImplementedError() en lugar de return NotImplemented, para empezar.

Lo que estás haciendo se llama "parche de monos", y está bien que lo hagas, siempre que sepas lo que estás haciendo, eres consciente de las consecuencias y estás de acuerdo con esas consecuencias. En general, limitas esto a corregir un error, o algún otro cambio donde sabes que la alteración del comportamiento sigue siendo correcta y compatible con versiones anteriores.

En este caso, como está parcheando una clase, puede cambiar el comportamiento fuera de los casos en que lo usa. Si otra biblioteca usa decimal, y de alguna manera se basa en el comportamiento predeterminado, podría causar errores sutiles. El problema es que realmente no lo sabe a menos que audite todo su código, incluidas las dependencias, y encuentre todos los sitios de llamadas.

Básicamente, hágalo bajo su propio riesgo.

Personalmente, me resulta más tranquilizador corregir todo mi código, agregar pruebas y dificultar el trabajo incorrecto (por ejemplo, usar clases de contenedor o funciones auxiliares). Otro enfoque sería instrumentar su código con su parche para encontrar todos los sitios de llamadas, luego retroceder y solucionarlos.

Editar - Supongo que debo agregar que la razón probable por la que evitaron las carrozas es que las carrozas no pueden representar con precisión todos los números, lo cual es importante si se trata de dinero.

+1

Solo una nota que el "return NotImplemented" es del propio paquete decimal.py. Las dos líneas que he agregado están entre los comentarios. Estoy de acuerdo con su enfoque, sin embargo, en esta implementación, Python permite comparaciones lógicamente insanas entre los objetos que asumimos que son ambos números. Hmm, otra idea podría ser generar un error en lugar de una conversión implícita, pero independientemente, creo que tengo que hacer algo ... –

+10

'return NotImplemented' es correcto y es el correcto, [documentación especificada] (http: // docs .python.org/reference/datamodel.html # emulating-numeric-types) a devolver para una comparación no admitida. Permite a Python intentar encontrar otra forma de hacer las cosas. – aaronasterling

+0

+1 por usar el término "parche de mono", que me llevó al término wikipedia, para encontrar que proviene de "parche de guerrilla", como en guerrilla =). – Tommy

3

Hay muy buenas razones para evitar carrozas. Con flotantes, no se pueden hacer comparaciones de manera confiable como ==,>, <, etc. debido al ruido de punto flotante. Con cualquier operación de coma flotante acumulas ruido.Comienza con dígitos muy pequeños que aparecen al final, por ejemplo, 1.000 ... 002 pero puede acumularse como 1.0000000453436.

El uso de str() puede funcionar para usted si no realiza muchos cálculos en coma flotante, pero si realiza muchos cálculos, el ruido de punto flotante será lo suficientemente grande como para que str() le proporcione el respuesta incorrecta.

En suma, si (1) que no hago que muchos cálculos de punto flotante, o (2) no es necesario hacer comparaciones como ==,>, etc. < entonces se podría estar bien .

Si quiere estar seguro, elimine todos los códigos de punto flotante.

+0

Existen muy buenas razones para evitar flotantes ** en programas contables ** como el de la pregunta. Los flotadores funcionan perfectamente bien para su propósito de representar ** cantidades ** aproximadas. – dan04

+1

@Dan, sí, la premisa de mi respuesta es que no se puede hacer == con flotadores. Si está representando cantidades aproximadas, entonces no está usando == ya que la igualdad no es aproximada. –

Cuestiones relacionadas