Tu computadora tiene huecos

Siempre pensamos en una computadora como una máquina precisa y veloz. Normalmente los errores son provocados por el programador, pero quizá os sorprenda saber que nuestra computadora, funcionando perfectamente, también puede llevarnos a cometer errores. Un ejemplo de ello es cuando sumamos 100 veces la cifra 0.1 y el resultado no es 10 en el lenguaje de programación Python:

01100

¿Hasta qué punto es un ordenador preciso? El lenguaje de programación Python no redondea los resultados obtenidos con números reales, lo que puede llevar a errores.
¿Hasta qué punto es un ordenador preciso? El lenguaje de programación Python no redondea los resultados obtenidos con números reales, lo que puede llevar a errores.

Este no es un problema único de Python. De hecho, podéis comprobarlo en vuestra hoja de cálculo favorita: colocad 100 celdas con 0.1 y sumadlas. Veréis que el resultado es 10. Ahora id a formato de la celda e indicad que queréis 15 decimales. El resultado ya no es 10.

Cuando trabajamos con números enteros, una computadora es precisa en el amplio sentido de la palabra. El único límite es el tamaño de los números que podemos almacenar. Si usamos 64 bits para almacenar un número, podemos llegar a representar más de un trillón de números, suficientes para tenernos entretenidos durante toda la vida del universo y más allá.

Todo cambia cuando nos movemos al mundo de los números reales. Os propongo un pequeño ejercicio mental:

¿Cuántos números reales hay en este intervalo?

intervalo01

Pues para empezar, en este intervalo nos cabría el trillón de números del que hablábamos antes, y muchos más, porque hay infinitos números reales entre el cero y el uno.

Cuando trabajamos con números reales y requerimos precisión, los programadores debemos tener muy en cuenta cómo representa los números una computadora para evitar errores.

Aquí tenéis dos funciones:

funciones

Muchos os habréis dado cuenta de que tienen truco: en realidad f y g son la misma función expresadas de formas diferentes. Pero veamos qué sucede cuando representamos estas funciones en el intervalo [0.98, 1,02].

grafica

 

La serie de cálculos que realiza la computadora para obtener los valores de f, provoca que el computador acumule errores en cada paso, obteniendo resultados que no parecen representar un polinomio. En cambio g se muestra suave como el culito de un bebé, como un polinomio profesional donde los haya.

Para entender lo que está pasando, veamos primero cómo almacena los números reales nuestra computadora. Como el sistema binario es poco intuitivo, vamos a imaginar una computadora que almacena los números en sistema decimal y un sistema de representación para jugar con él:

cuadronumeros1

Lo primero que hemos hecho ha sido normalizar los números, quedándonos con el signo, la mantisa y el exponente. La mantisa, o fracción, serán las cifras más significativas del número almacenadas como un número entre cero y uno. El exponente es el que nos permitirá expresar números de diferentes tamaños. A esta forma de representar los números se le denomina coma flotante.

Nuestra mantisa y exponente tienen límite, en nuestro ejemplo tenemos 5 dígitos para la mantisa y 2 para el exponente. Ese es el tamaño máximo que podrá manejar nuestro sistema de juguete.

Ahora bien, si tenemos un límite de cinco dígitos en nuestra mantisa, implica que nuestros números tendrán como mucho cinco cifras significativas.

Por ejemplo, si queremos representar 12345.1 y 12345.2 tendremos lo siguiente:

dosnumeros

Perdemos la última cifra de ambos números y además obtenemos el mismo número.

En nuestro sistema hay huecos entre números. Cuando usamos exponente 4, hay huecos de tamaño unidad entre dos números consecutivos.

Si por ejemplo usamos exponente 4, tenemos huecos de tamaño 0,1. El hueco varía según el exponente que usemos, en nuestro sistema de juguete será:

hueco

Pues bien, en la computadora los números se guardan de forma parecida, pero usando un sistema binario, en el que ya no usamos una base 10 para el exponente, sino 2.

Bits usados para el almacenamiento de coma flotante de 64 bits. 1 bit para el signo, 11 para el exponente y 52 para la mantisa.
Bits usados para el almacenamiento de coma flotante de 64 bits. 1 bit para el signo, 11 para el exponente y 52 para la mantisa.

El exponente puede variar entre -1022 y 1023 lo que nos permite expresar números cercanos al cero y números tan grandes como un uno seguido de 300 ceros.

Aquí tenéis los huecos que podemos encontrar cuando usamos los dos sistemas más habituales para almacenar números reales en el ordenador:

Tamaño de huecos entre números para precisión simple y doble en coma flotante. Se expresa en decimal.
Tamaño de huecos entre números para precisión simple y doble en coma flotante. Se expresa en decimal.

Y estos huecos, ¿cómo afectan a nuestra gráfica? Uno de los errores más habituales en cálculos con coma flotante en ordenadores es el que se produce cuando realizamos operaciones con números de órdenes de magnitud muy diferentes.

Si queremos sumar estos dos números:

suma

Primero tenemos que expresarlos con el mismo exponente, que será el más alto de los dos. Así tendremos:

suma2

Se ha perdido información en la suma, de hecho nos hemos quedado con una sola cifra significativa de nuestro segundo número.

Cuando calculamos los valores del polinomio de nuestra gráfica mediante f, estamos haciendo sumas y restas con números del orden de centenas, mientras que el resultado final tiene 14 ceros antes de la primera cifra significativa. Se van produciendo errores de redondeo en cada una de las operaciones de suma y resta que se acumulan, provocando el efecto de dientes de sierra que puede verse en la gráfica.

Quizá penséis que esto se solucionaría utilizando más bits para almacenar nuestros números. El problema es que también queremos velocidad, y 64 bits es el tamaño de número que ahora mismo pueden manejar nuestros microprocesadores de forma nativa. Es decir, pueden coger dos números de 64 bits y hacer una operación con ellos. Si queremos usar tamaños mayores, implica realizar cálculos en varios pasos y perder velocidad, aparte de que no soluciona todos los problemas.

¿Y por qué 0.1 sumado 100 veces no es 10? Pues aquí aparece un error que nos provoca el cambio a sistema binario. Hay números decimales, como el 0.1, que no se pueden representar en sistema binario. Cuando intentamos representar 0.1 en sistema binario, obtenemos una sucesión infinita de ceros y unos en la mantisa, por lo que 0.1 tiene un error de redondeo, independientemente del número de bits que usemos para representarlo en coma flotante.

En cualquier caso, no es un problema que nos afecte en nuestro día a día; si calculamos la circunferencia de la tierra con un número pi guardado con precisión de 64 bits, el error producido por utilizar ese número sería menor que el grosor de un folio. Bastante preciso, ¿verdad?

 

Más información

El estándar IEEE para coma flotante de 64 bits y 32 bits define cómo se utilizan los números en la mayoría de las computadoras de hoy en día. Aquí tenéis un resumen.

http://es.wikipedia.org/wiki/IEEE_coma_flotante

Si queréis profundizar más en el tema, podéis visitar esta guía http://puntoflotante.org/ en la que se habla del formato y errores comunes.

Imágenes wikicommons utilizadas (el resto son de creación propia):

http://commons.wikimedia.org/wiki/File:IEEE_754_Double_Floating_Point_Format.svg

http://commons.wikimedia.org/wiki/File:IEEE754.png?uselang=es

Fuentes consultadas:

Análisis numérico con aplicaciones. Gerald, Curtis F.; Wheatley, Patrick O. Editorial Addison Wesley.

IEEE Standard unifies arithmetic model. Cleve Moler

http://www.mathworks.es/company/newsletters/news_notes/pdf/Fall96Cleve.pdf

Este es un post de nuestro socio J. M. Morales «El Zombi de Schrödinger» que escribe el blog «Cuanto Zombi»

3 Comments

    2 de julio de 2015

    […] Tu computadora tiene huecos […]

    3 de julio de 2015

    Pedazo de artículo

    Muchas gracias Sr. Zombie

    4 de julio de 2015

    Hola.

    Me gustaría, ante todo, felicitar al autor por su sencillez al abordar esta cuestión, nada sencilla de comprender por un lego.

    En todo caso, me gustaría matizar que el texto no es del todo correcto. Espero explicarme…

    Una computadora (esto es, la parte física, lo que vemos y podemos tocar: el hardware), no tiene «huecos»… excepto si la máquina está mal diseñada, como con el famoso «bug» (error) del Pentium.

    Tampoco es habitual encontrarse con errores o «huecos» en los lenguajes de programación (como el Python que comentas), aunque han existido y, supongo, existirán en el futuro. Se suelen corregir mediante versiones del lenguaje.

    También se pueden cometer errores en los compiladores o intérpretes (como el de Python) que produzcan «huecos» (es decir, valores erróneos desde el punto de vista matemático) en las operaciones, bien fundamentales (suma, resta, multiplicación o división) o bien provenientes de un error de codificación en alguna de las funciones matemáticas (raíz cuadrada, exponencial…). Como en el caso del lenguaje, los compiladores e intérpretes se suelen corregir sacando «parches» o nuevas versiones.

    Lo que sí que puede tener (y los tiene; y muchos, además) es el software.

    Tanto cuando se realizan aplicaciones (una calculadora, una hoja de cálculo…) como cuando se almacena (en memoria o disco) el programador puede cometer errores (de redondeo, entre otros muchos) que den como resultado valores erróneos, matemáticamente hablando. Es algo inevitable y, en ocasiones, difícil de detectar.

    En fin, disculpas por este tocho y gracias, de nuevo, por el artículo.

Comments are closed.