2012-01-15 11 views
5

El siguiente código python calcula el número de iteraciones para hacer cosas basadas en algunas variables.Aritmética de punto flotante: ¿posible dependencia insegura en comparación específica?

# a - b - c is always a multiple of d. 
    i = (a - b - c)/d 
    while i: 
    # do stuff 
    i -= 1 

Las variables serán todos del mismo tipo, es decir solamente ints o floats o lo que sea. Mi preocupación es si funcionará correctamente si los valores son floats. Sé lo suficiente como para considerar siempre las trampas de confiar en los valores exactos de flotación. Pero no puedo decir si lo anterior es peligroso o no. Puedo usar i = int(round((a - b - c)/d)), pero tengo curiosidad por comprender mejor a los flotadores.

Todo se reduce a lo siguiente: a - b - c es un múltiplo exacto de d. Así que estoy confiando en (a-b-c)/d para convertirme en un valor i del que puedo restar 1 y obtener el número esperado de iteraciones en el ciclo while, con la suposición implícita de que i == 0 se convierte en verdadero. Es decir, ¿se pueden disminuir los múltiplos calculados como este en 1 para llegar exactamente a 0?

Me gustaría saber no solo si no es seguro, pero lo más importante, ¿qué debo entender sobre el punto flotante para resolver una pregunta como esta? Si alguien sabe con decisión si esto es seguro o no, ¿sería posible explicar cómo?

+0

Si crees que 'i' debe tener un valor entero, ejecútalo en un entero. ¡Eso al menos garantizará que tu bucle terminará! – katrielalex

Respuesta

4

Puede utilizar el decimal module para tener una idea de lo que "esconde" entre un número de coma flotante como 0.3:

>>> from decimal import Decimal 
>>> Decimal(0.3) 
Decimal('0.299999999999999988897769753748434595763683319091796875') 

Tenga en cuenta que Python 2.7 cambió cómo se escriben los números de coma flotante (cómo funciona repr(f)) de modo que ahora muestra la cadena más corta que dará el mismo número de coma flotante si lo hace float(s). Esto significa que repr(0.3) == '0.3' en Python 2.7, pero repr(0.3) == '0.29999999999999999' en versiones anteriores. Lo menciono porque puede confundir aún más las cosas cuando realmente quiere ver qué hay detrás de los números.

Con el módulo decimal, podemos ver el error en un cálculo con los flotadores:

>>> (Decimal(2.0) - Decimal(1.1))/Decimal(0.3) - Decimal(3) 
Decimal('-1.85037170771E-16') 

Aquí se podría esperar (2.0 - 1.1)/0.3 == 3.0, pero hay una pequeña diferencia que no sea cero. Sin embargo, si lo hace el cálculo con números de punto flotante normales, entonces lo hace llegar a cero:

>>> (2 - 1.1)/0.3 - 3 
0.0 
>>> bool((2 - 1.1)/0.3 - 3) 
False 

El resultado se redondea en algún punto del camino desde 1.85e-16 es distinto de cero:

>>> bool(-1.85037170771E-16) 
True 

No estoy seguro de dónde se produce este redondeo.

En cuanto a la terminación de bucle en general, entonces hay una pista que puedo ofrecer: for floats less than 253, IEEE 754 can represent all integers:

>>> 2.0**53  
9007199254740992.0 
>>> 2.0**53 + 1 
9007199254740992.0 
>>> 2.0**53 + 2 
9007199254740994.0 

El espacio entre los números representables es 2 a partir de 2 a 2 , como se muestra arriba . Pero si su i es un número entero menor que 2 , entonces i - 1 también será un número entero representable y eventualmente llegará a 0.0, que se considera falso en Python.

+0

El redondeo se produce en 'Decimal (2.0) - Decimal (1.1)', que da como resultado 'Decimal (' 0.8999999999999999111821580300 ') 'mientras que el valor exacto de' doble 'es' 0.899999999999999911182158029987476766109466552734375'. Utilice '(2.1 - 1.2)/0.3' para producir un valor no exactamente tres con' double's. –

3

Le daré una respuesta independiente del idioma (realmente no conozco Python).

Existen varios problemas potenciales en su código. En primer lugar, esto:

(a - b - c) 

Si a es (por ejemplo) 10 , y b y c son ambos 1, entonces la respuesta será 10 , no 10 -2 (I estoy asumiendo float de precisión simple aquí).

Luego está esto:

i = (a - b - c)/d 

Si numerador y denominador son números que no pueden representarse exactamente en punto flotante (por ejemplo, 0.3 y 0.1), entonces el resultado podría no ser un número entero exacto (IT podría ser 3.0000001 en lugar de 3). Por lo tanto, su bucle nunca terminará.

Luego está esto:

i -= 1 

De manera similar a lo anterior, si i es actualmente 10 , entonces el resultado de esta operación será todavía 10 , por lo que el bucle nunca terminará.

Por lo tanto, debe considerar realizar todos los cálculos en aritmética de enteros.

+0

Sí, esto muestra la inviabilidad de usar flotadores como este de la manera más simple posible. Lo deja muy claro, gracias. – porgarmingduod

+0

@MartinGeisler: Sí, explícitamente hice esta suposición (no estoy familiarizado con los tipos nativos de Python). Pero como dices, todo lo que realmente ocurre con la doble precisión es que las magnitudes cambian; los problemas todavía existen –

1

Tiene razón en que podría haber una no convergencia en cero (al menos para más iteraciones de las que pretende). ¿Por qué no hacer que su prueba sea: while i >= 1. En ese caso, como en el caso de los enteros, si tu valor i baja por debajo de 1, el ciclo terminará.

Cuestiones relacionadas