Lo que todo científico —o programador— debería saber sobre la aritmética de coma flotante

Los cálculos que realizamos en nuestros ordenadores no son exactamente los mismos cálculos que realizamos con nuestra cabeza. En nuestra cabeza, podemos dividir 1 entre 3, y sabemos que es exactamente un tercio (1/3), o bien aproximadamente 0,333333…, indicando los puntos suspensivos una repetición del periodo.

Si estamos haciendo un cálculo con números en coma flotante, sabemos que 1 – 0,9 – 0,1 es igual a cero (después de todo, 0,9 más 0,1 debe ser 1), y sabemos que eso es equivalente a la operación 1 – 9/10 – 1/10.

Pero si usamos un ordenador para calcularlo, por ejemplo, usando Python

mac:~ user$ python
Python 2.3.5 (#1, Mar 20 2005, 20:38:20)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1809)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> print 1 – 0.9 – 0.1
-2.77555756156e-17

¿Por qué el resultado no es 0?

La respuesta, básicamente, es que los números que son periódicos puros en una base no tienen por qué serlo en otra… y en este caso, por ejemplo, 1/10, en binario, es un número periódico, por lo que al truncarlo siempre tendremos un error que dependerá de la precisión numérica que estemos utilizando.

En un interesantísimo artículo, titulado What Every Computer Scientist Should Know About Floating-Point Arithmetic, y que ahora forma parte de la Numerical Computation Guide de Sun, David Goldberg se adentra en estos aspectos, y nos explica cómo establecer comparaciones entre números en coma flotante, y cómo tener en cuenta la propagación de errores de redondeo que hace que se produzcan resultados como el anteriormente expuesto.

Por cierto, el artículo lo encontré en la bitácora NSBlog de Mike Ash, una bitácora muy recomendable que encontré gracias a la no menos recomendable Lista Enlazada de John Gruber…

[Actualización 1] El artículo de la guía de cálculo numérico de Sun habla de cómo se describen los números en coma flotante en los diferentes lenguajes de programación, y habla de conceptos como exponentes y mantisas, pero sin especificar sus tamaños. Existe un estándar, el IEEE-754, que describe justamente los tamaños y forma de uso de los diferentes formatos de coma flotante (simple precisión, doble precisión, cuádruple precisión…), y podéis encontrarlo en los enlaces.

Si se calcula el valor absoluto del logaritmo en base 2 del error relativo cometido al calcular 1 – 0,9 – 0,1, obtendríamos el tamaño de la mantisa de un número en coma flotante de doble precisión (52):

>>> from math import *; print abs(log(2.77555756156e-17/0.1)/log(2))
51.6780719051

Recordemos que el logaritmo en base n de un número x se puede calcular siempre como el logaritmo en cualquier base de x, entre el logaritmo en la misma base de n:

logn(x) = log(x)/log(n).

Enlaces


Comentarios

11 responses to “Lo que todo científico —o programador— debería saber sobre la aritmética de coma flotante”

  1. Avatar de YoMismo
    YoMismo

    Hay cosas que es mejor no saber… ahora a ver como duermo yo esta noche, si puede haber una coma flotante dividiendo decimales como loca suelta por ahí…

    Mundo peligroso.

  2. De todos es conocido que las comas flotantes son de todo menos heterosexuales. 😀

  3. Avatar de No importa
    No importa

    Algo que deberían saber los programadores de Python:

    Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
    [GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.

    >>> print 9/10
    0
    >>> print 1/10
    0
    >>> print 9.0/10
    0.9
    >>>
    >>>
    >>> print 1 - 9.0/10 - 1.0/10
    -2.77555756156e-17

    Al dividir numeros enteros, Python devuelve la division entera.

    >>> print 555/10
    55
    >>> print (float)(555)/10
    55.5
    >>>
    >>>
    >>> print 1 - (float)(9)/10 - (float)(1)/10
    -2.77555756156e-17

  4. Ostras, qué metedura de pata… y además me daba un 1 y yo me quedaba tan campante… ¡arreglado, No Importa! Bueno, claro que importa, pero te doy las gracias a ti 😉

  5. Avatar de esteban
    esteban

    Interesante post, en Java ocurre lo mismo (supongo q en los demás lenguajes tbien :P):

    double a = 1 - 0.9 - 0.1;
    System.out.println(a);
    -2.7755575615628914E-17

  6. Y además, esos errores de redondeo hacen posible que ciertas propiedades lógicas, como las reglas de la conmutatividad y la asociatividad, no sean ciertas. Por ejemplo, en python 2.3.5:

    >>> print 1 - 0.1 - 0.9
    0.0
    >>> print 1 - 0.9 - 0.1
    -2.77555756156e-17

  7. Avatar de esteban
    esteban

    En Java también!!!

    double a = 1 - 0.1 - 0.9;
    System.out.println(a);
    0.0

  8. Eso en HyperTalk no pasa…

    put 1.0 - 0.9 - 0.1
    0

    ˆ_ˆ

  9. Je, je, Zydeco, tienes razón, pero es que HyperTalk hace cálculos con mayor precisión (80 bits) durante el cálculo y redondea a la hora de dar resultados…

    Sin embargo…

    put atan(tan(1.0000001))
    1

    Cuando el resulado debería ser 1.0000001…

    En cambio, con Python el resultado es correcto hasta con 11 ceros…

  10. Google también lo hacía en tiempos (cuando lo de ‘Google no sabe restar’).

  11. Cierto, RaveN, y lo han solucionado, al parecer, buscando patrones, porque si apuras, obtienes error, pero no menor que para doble precisión, sino exactamente igual:

    <a href="http://www.google.com/search?hl=es&rls=es&q=1+-+0.9999+-+0.0001&quot; target="_blank"

To respond on your own website, enter the URL of your response which should contain a link to this post’s permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post’s URL again. (Find out more about Webmentions.)

Descubre más desde Memoria de Acceso Aleatorio

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo