Representación de flotantes
Clase 11 de 31 • Curso de Introducción al Pensamiento Computacional con Python
Contenido del curso
Clase 11 de 31 • Curso de Introducción al Pensamiento Computacional con Python
Contenido del curso
La mayoría del tiempo los números flotantes (tipo float) son una muy buena aproximación de los números que queremos calcular con nuestras computadoras. Sin embargo, "la mayoría del tiempo" no significa todo el tiempo, y cuando no se comportan de esta manera puede tener consecuencias inesperadas.
Por ejemplo, trata de correr el siguiente código:
x = 0.0 for i in range(10): x += 0.1 if x == 1.0: print(f'x = {x}') else: print(f'x != {x}')
Es probable que te hayas sorprendido con el resultado. La mayoría de nosotros esperaríamos que imprimiera 1.0 en vez de 0.999999999999. ¿Qué es lo que pasó?.
Para entender qué es lo que pasó tenemos que entender que es lo que pasa en la computadora cuando realizamos cómputos con números flotantes. Y para eso necesitamos entender números binarios.
Cuando aprendiste a contar, lo que en realidad aprendiste es una técnica combinatoria para manipular los siguientes símbolos que le llamamos números: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
La forma en la que funciona esta técnica es asignando el número 10 a la 0 al número de la extrema derecha, 10 a la 1 al siguiente, 10 a la 2 al siguiente y así sucesivamente. De tal manera que el número 525 es simplemente la representación de (5 * 100) + (2 * 10) + (5 * 1).
Esto nos dice que el número de números que podemos representar depende de cuanto espacio tengamos. Si tenemos un espacio de 3, podemos representar 1,000 números (10 elevado a la 3) o la secuencia del 0 al 999. Si tenemos 4, podemos representar 10,000 (10 elevado a la 4) o la secuencia del 0 al 9,999. De manera general podemos decir que con una secuencia de tamaño n, podemos representar 10 elevado a la n números.
Los números binarios funcionan de la misma manera (de hecho cualquier número en cualquier base, por ejemplo, octales o hexadecimales). La única diferencia es cuántos símbolos tenemos para representar. En binario nada más tenemos 0, 1; en hexadecimal tenemos 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f.
De esta manera podemos decir que el número de la extrema derecha es cantidad_de_simbolos**0, cantidad_de_simbolos**1, cantidad_de_simbolos**2, etc. Por lo que en binario, que nada más tenemos 2 símbolos, decimos 2**0, 2**1, 2**2, etc. Por ejemplo el número binario 101 es la representación de (1 * 4) + (0 * 2) + (1 * 1), es decir 5.
Esta representación nos permite trabajar con todos los números positivos enteros dentro del computador, pero ¿Qué hacemos con los negativos y los racionales?.
El caso de los números negativos es sencillo: simplemente agregamos un bit adicional que representa el signo y la añadimos en la extrema izquierda. Por lo que el número 0101 sería +5 y el número 1101 sería -5.
El caso de los racionales es más complejo. En la mayoría de los lenguajes de programación modernos los racionales utilizan una implementación llamada punto flotante. ¿Cómo funciona esta representación?.
Antes de pasar a binario, vamos a pretender que estamos trabajando con una computadora basada en decimales. Un número flotante lo representaríamos con un par de enteros: los dígitos significativos y el exponente. Por ejemplo, el número 2.345 se representaría como (2345 * 10**-3) o (2345, -3).
El número de dígitos significativos determinan la precisión con la que podemos representar número. Por ejemplo si nada más tuviéramos dos dígitos significativos el número 2.345 no se podría representar de manera exacta y tendríamos que convertirlo a una aproximación, en este caso 2.3.
Ahora pasemos a la verdadera representación interna de la computadora, que es en binario. ¿Cómo representarías el número 5/8 o 0.625? Lo primero que tenemos que saber es que 5/8 es en realidad el número 5 * 2**-3. Por lo que podríamos decir (101, -11) (recuerda que el número 5 es 101 en binario y el 3 es 11).
Regresemos a nuestro problema inicial: ¿Cómo representaremos 1/10 (que escribimos en Python cómo 0.1)? Lo mejor que podemos hacer con cuatro dígitos significativos es (0011, -101) que es equivalente a 3/32 (0.09375). ¿Qué tal si tuviéramos cinco dígitos significativos? La mejor representación sería (11001, -1000) que es equivalente a 25/256 (0.09765625). ¿Cuántos dígitos significativos necesitamos entonces? Un número infinito. No existe ningún número que cumpla con la siguiente ecuación: sim * 2**-exp.
En la mayoría de las implementaciones de Python tenemos 53 bits de precisión para números flotantes. Así que los dígitos significativos para representar el número 0.1 es igual a:
11001100110011001100110011001100110011001100110011001 que es equivalente al número decimal: 0.1000000000000000055511151231257827021181583404541015625
Muy cercano a 1/10 pero no exactamente 1/10. Ahora ya sabemos la razón de esa respuesta tan extraña. Hay muy pocas situaciones en la que 1.0 es aceptable, pero 0.9999999999999999 no. Pero ¿Cuál es la moraleja de esta historia?
Hasta ahora hemos verificado igualdad con el operador ==. Sin embargo, cuando estamos trabajando con flotantes es mejor asegurarnos que los números sean aproximados en vez de idénticos. Por ejemplo x < 1.0 and x > 0.99999.
Carlos Enrique Pereda Mieses
Paulina Yañez Rubio
David Alberto Rodriguez Muñoz
Francisco Karriere
Marcos ismael Caballero reyes
Miguel Alexander Vásquez Ortega
Daniel Alejandro Cumaco Robayo
Felipe Andres Torres Haro
Luis Ruiz Ramos
Josue Granados
Harold Giovanny Uribe Romero
Jose Suarez
Jhon Freddy Puentes Nuñez
Facundo Velastiquí Domene
Lorenzo Enrique Piñango Cerezo
David Buitrago
Fernando Gabriel
Royer Guerrero Pinilla
Facundo Soto
Rodrigo Mozzo
Pablo Andres Fernandez Cari
JOSE EDUARDO MELENDEZ CRUZ
ANDRES FELIPE MAYA RESTREPO
Marcelo Jara
Carlos Andrés Valderrama Pabón
Kevin Morales
Johanna Sanchez Vallejo
Sergio Alejandro Alvarado Parada
Diego Buesaquillo
César Andrés Baudi Ventura
Jorge Alexander Cotrina Vilchez
Edwar Sanchez
Jesús David Jaramillo Rincón
Carlos Eduardo Gomez García
Ignacio Crespo
Cesar Hernández Ramírez
Daniel Valenzuela
David Gallego
Puse los números binarios para verlo de otra forma:
En binario, intenten sumar varios decimales para llegar a 0.1 verán no es posible. Necesitarían una infinita cantidad de números decimales para ello y ++la memoria de una computadora finita++.
Si es que te parece muy extraño no poder representar un número decimal y difícil de entender, piensa que **este problema ya lo has tenido antes toda tu vida **en el sistema decimal con 1/3. Para ese número necesitamos infinitos dígitos decimales para representarlo.
Mi primer aporte para la comunidad :)
Genial tu explicación.
Primer aporte, pero muy bueno.
Conclusión se puede decir que los flotantes son inexactos por lo tanto se los debe tratar como tal. Esto quiere decir que tenemos que manejarlos de manera aproximada. "cercano a.." por esto se recomienda no utilizar igualdad exacta == sino mayor que o menor que como el ejemplo de si queremos un numero igual a 1 tenemos que utilizar x < 1.000 y x > 0.999 para obtener una equidad aproximada.
pd. Me gusta que estos temas sutiles pero muy importantes se plasmen por escrito, no todo tiene que ser vídeo como piden algunos, el video lo pasas a 2X y luego no recuerdas de que va y terminas utilizando un == para flotante y no te funciona bien tu código. Paciencia que leer esta bueno también 😁🤓
gracias por el aporte
Gracias por el aporte, porque estaba medio en el aire 😌
Para los que quedaron perdidos en la ultima parte partamos por esto:
Primero
Como pasamos el mumero (5) en base 10 a base 2, osea a binario?, pues dividiendo el numero entre dos y almacenando el modulo asi:
5 / 2 = 2 5 % 2 = 1 <--- Numero que agregamos al binario
2 / 2 = 1 2 % 2 = 0 <--- Segundo numero que agregamos al binario
5 en base 10 = 101 en base 2
0.1 * 2 = 0.2 # cogemos la parte entera para nuestro numero binario quedando (0.0) 0.2 * 2 = 0.4 # cogemos la parte entera para nuestro numero binario quedando (0.00) 0.4 * 2 = 0.8 # asi sucecivamente (0.000) 0.8 * 2 = 1.6 # agregamos el (1) de la parte entera (0.0001), y seguimos operando con la parte decimal 0.6 * 2 = 1.2 # otra ves agregamos el (1), quedando (0.00011) y operamos la parte decimal 0.2 * 2 = 0.4 # como se daran cuenta el ciclo se esta repitiendo nos volvio a dar parte decimal de (0.2)
0.0001100110011001100110011001100110011........... infinito
Y como python tiene acceso a memoria finita, trata de recortar el binario hasta el maximo de digitos que puede sostener, por ejemplo si solo pudiera sostener 8-bits decimalel (8 digitos decimales)
Solo podriamos tener los primero 8 digitos decimales binarios (0.00011001) los cuales a decimal serian = 0.09765625
Si acogieramos mas bits o digitos decimales, el numero se proximaria mas, pero consume mas memoria
Si tienen alguna duda, estare atento, gracias
excelente ;) me costó entender la explicación de arriba, y lo tuyo lo complementó caleta ;) te pasaste ♥
Gracias.
Acabo de escribir un tutorial acerca de por que pasa esto y como lidiar con ello! https://platzi.com/tutoriales/1764-python-cs/9448-por-que-obtengo-resultados-inesperados-con-los-float-como-lidiar-con-eso/ Explicado de manera sencilla y paso a paso. Fueron varios dias de investigación. Espero y les ayude! 👍
Que buen tutorial Josue!!!
Gracias por el tutorial Josue, ayudo un montón
Hola compañeros! Acá mis notas de como entender pasar de decimal a binario, Espero les sriva.
¡GRACIAS!
Genial!
Honestamente no entendí 😢
Por favor mejoren esta lectura y si graban una clase con la explicación mucho mejor
Algunas veces todo no es video, si quieres entrar a la industria debes acostumbrate a leer y bastante
@ROY3R Entiendo eso pero... al diablo, para eso pago una suscripción a un servicio en video.
Pago Platzi y aprendo en google y youtube. Por favor mejorar las explicaciones que van por texto. Gracias!
x2
x3
Aunque son temas que sé por la universidad, creo que seria mucho mas entendible el articulo si se utilizaran imágenes mínimamente para visualizar las expresiones. Lo ideal seria un video ya que es un tema que no todos podemos digerir tan facilmente...
Temas con este nivel de complejidad igual quedarian mejor en un video explicado con peras y manzanas en mi humilde opinion :D
El artículo tiene muchos errores de caracteres y se pierde mucho la explicación.
Ya quedó corregido :)
Me sumo, a la petición de revisión de este articulo. Para poder tener un hilo conductivo al momento de la lectura.
La verdad, esta lectura es poco entendible. Se puede mejorar bastante para hacerla más sencilla
Creo entender el core del artículo, pero hubiese sido muchísimo mejor haberlo explicado en un vídeo. No es un tema que sea entendible para todos.
Hubiera sido mejor una explicación en video usando la pizarra interactiva que un articulo para entender este concepto
Quizás esto ayude un poco a entender: 1/3 = 0.3333333333; entonces si multiplicamos 1/3 * 3 = 1 y si lo hacemos con los decimales (float) 0.333333 * 3 = 0.9999999; con lo que llegamos a la conclusión de que (0.9999999 == 1)
Una explicación un poco compleja, es mejor que realizaran un vídeo realizando la explicación.
sí, toda la vida, aunque se entiende completamente, requirió de media hora de mi tiempo, un buen video hubiese explicado eso mejor en 5 veces menos tiempo.
Comprendo, algo importante a mencionar es que, cualquier número con exponente negativo representa un número decimal, por ejemplo (recordemos que doble asterisco en Python significa exponente): 10**-1 == 0.1, el primero número (10) es la base numérica (sistema decimal), el segundo número (-1) es el número de decimales con los que queremos ser precisos, en este caso, para el sistema decimal, el exponente negativo (-1) generará un resultado con un solo decimal, si pusiéramos 10**-2 entonces el resultado es de dos decimales: 0.01 y así sucesivamente.
.
Ahora, tomando como referencia lo explicado en la clase, un número con punto flotante (un número decimal) se compone de:
.
. Y estos dos se multiplican. Los dígitos significativos son el número entero que queremos convertir a decimal, mientras más números y mayor el exponente, el decimal será más preciso:
1234 * 10**-1 = 123.4 (El exponente genera un solo decimal a partir de los digitos significativos) 1234 * 10**-2 = 12.34 (El exponente genera dos decimales a partir de los digitos significativos) 1234 * 10**-3 = 1.234 (El exponente genera dos decimales a partir de los digitos significativos)
Mientras más dígitos significativos tengamos, mayor exponente podremos poner para que el resultado sea más preciso. Lo que aún no entiendo es como se relacionan estos números para la base 2 (binario): 5 * 2**-3, no entiendo cómo se relaciona ese 5 con el número 0.625 (donde en decimal 625 serían los dígitos significativos) 🤔
Excelente aporte compañero! Creo que la explicación del profe fue bastante compleja de entender y creo que era un tema ideal para explicarlo en video y ponerlo en práctica en Python.
Los numeros significativos son 3 cifras, no son 625. Recuerda que estamos en los binarios, cuando manejamos esa ecuacion, no tenemos en mente que las 3 cifras son 625. Lo que en realidad hay ahí es un 101, por eso es que cumple con las 3 cifras, y son iguales a 5.
Si bien se entiende el artículo y la idea principal, hubiera sido mejor crear el video con mostrando el problema y la explicación de todo el asunto.
De manera muy general, como los numeros en la computadora son representados en binarios, toma mas espacio representar un numero racional en binario que un numero entero. Ademas sabiendo que python tiene un limite de 53 bits para la precision de los numeros con decimales, no se puede entonces llegar a calcular exactamente el 1.0, asi que con la capacidad de python se puede llegar es al numero dado en el articulo.