Diferencia entre numero de coma flotante y decimal

5 y 5.0 no son iguales desde el punto de vista de la programación. A diferencia de otros lenguajes,  donde se indica su tipo, cuando pasamos un dato a Python, este  intenta determinar a que tipo de dato corresponde

 La lógica que aplica Python, es considerar que si haces una división entre enteros, estas esperando un entero como resultado, y eso es lo que ofrece.

Pero si lo que esperamos es un resultado en fracción, hay que decirle a Python que al menos uno de elloslo es, haciendo una declaración del tipo  foo  = x.0

Entonces si , el  lenguaje retornará un valor del tipo flotante, al dividir. 

En Python 3, esto ya funciona de modo automático, e incluso sin colocar el punto devolverá un flotante, por una cuestión de optimización del funcionamiento del lenguaje.

 Una forma de ayudar a Python, es indicando que uno de los elementos de la división es del tipo float, haciendo una conversión sencilla del tipo z = float (x) / y  de modo que transformamos el valor  de x en un número de coma flotante, antes de operar con el.  

Este comporamientos heredado de C, tiene su base en la diferencia que existe al operar a bajo nivel con números enteros, de coma flotante o decimales.

Conocer esta diferencia sobre todo  nos hará entender mejor lo que hacemos.

Los números de coma flotante, no representan todas las  fracciones decimales, sino que algunas son redondeadas; sin embargo el  Decimal, es capaz de representar estas, obedeciendo al limite de precisión qué le indiquemos. 

La diferencia entre coma flotante y decimal

Coma flotante

Como decía antes, los números de coma flotante obvian ciertas representaciones.

Estos se componen de un signo, ya sea negativo o positivo, la mantisa, que es el número binario de un solo dígito seguido por una parte fraccional y el exponente que indica en qué lugar se ubica la coma decimal.

Por ejemplo, el número 1,25 tiene signo positivo, una mantisa de 1,01 (en binario) y un exponente de 0.

 El número 5 también es positivo, y posee la misma mantisa,  y su exponente es 2, pero cómo hay que multiplicar su mantisa por 4 (2 a la 2); 1,25 x 4 vale 5.

Hay que decir que la norma IEEE 754, que define el tratamiento de los números con coma flotantes, esta presente en casi todos los sistemas. 

Debido a esto es lógico, que el tipo de datos de coma flotante basados en  C doublé, es común que aparezca implementado en un número de 64 bits del IEEE 754, que al mismo tiempo emplea un espacio de  52 bits para la mantisa.

Esto equivale a  que las cifras de coma flotante, sólo se pueden definir  hasta 54 bits de precisión.

Todo lo que exceda de ese espacio, será recortado,.

El problema surge cuando se requiere o se posee una salida en base 10, que como sucede a menudo con fracciones en base 10, estas son del tipo periódico en base 2.

Entonces como expresar estas fracciones periódicas, sin exceder los 52 bits pues creando una ligera imprecisión, que puede acarrear inexactitudes importantes a la larga.

Muchas veces esto es imperceptible y puede dejarse pasar. Si consideramos la magnificencia de C al devolver los datos, y la poca falta que puede hacernos una exactitud rigurosa, puede no ser necesario incluso considerarlo.

No obstante en cálculos aritméticos exigentes, esta omisión puede requerir soluciones complejas.  

 Decimal

Decimal, es un modulo creado para resolver esta situación.

Posee en su estructura las clases Decimal y Context, que le aportan una estructura útil, para los que estrañaban algo asi en Python 2.

Las instancias de estas clases, se reparten la responsabilidad de ofrecer una solución robusta para construir el redondeo.

Mientras la clase Decimal representa a los números; Context se usa para agrupa aquellas propiedades, que definirán el resultado esperado, como son el modo de redondeo y la precisión

Una vez creado el numero es inmutable, por lo que no puede cambiarse su valor, y  es muy ductil ya que las instancias de Decimal se pueden crear a partir de enteros, cadenas y tuplas:

import decimal 
decimal.Decimal(567)
Decimal("1972")
>>>decimal.Decimal("9.8")
Decimal("1.1")

En el caso de las tuplas, se puede complejizar un poco mas y ofrecerle a Python en una tupla el signo, la mantisa como tupla de dígitos decimales, y el exponente:

>>> decimal.Decimal((1, (2, 3, 2, 3), -1))
Decimal('-232.3')

En estos casos hay que respetar que el  bit de signo es Booleano, por lo que 0 significa positivo y 1 significa negativo.

¿Y los flotantes?

Otro punto importante es que los creadores de Python dejaron fuera la conversión directa de números de coma flotante. La solución es convertir el número de coma flotante en una cadena con la precisión esperada y luego pasar esta al constructor de Decimal:

>>> f = 2.2
decimal.Decimal(str(f))
Decimal('2.2')
>>> decimal.Decimal('%.8f' % f)
Decimal('2.20000000')
La única limitación que nos da decimal ahora es que el exponente sea un numero entero.  Una operación en la que no se cumpla ese principio arrojara errores de estos tipos 
decimal.InvalidOperation: x ** (non-integer)
unsupported operand type(s) for ** or pow(): 'str' and 'str'

Otra muestra de la fortaleza de decimal es que permite combinar instancias de Decimal con enteros,  sin embargo esto no es posible con números de coma flotante:

Por otro lado, no todo podía ser color rosa, cuando utilizamos los modulos de Python math y cmath, estos se convierten a números de coma flotante antes de operar, y por supuesto el resultado del calculo será también un numero de coma flotante 

Usando Context

Las instancias de Context encapsulan las propiedades que deseamos asignar a nuestra operación de redondeo

  • prec es la precisión, el número de decimales.
  • rounding especifica el modo de redondeo. Existen varias constantes para las diferentes opciones: ROUND_DOWN (hacia cero), ROUND_CEILING (hacia arriba),   ROUND_HALF_EVEN (al más cercano), ROUND_UP, ROUND_05UP    ROUND_HALF_DOWN  ROUND_HALF_UP ROUND_FLOOR
  • • traps es un diccionario, que indica como tratar los errores.

Dentro de esto tenemos la opción de utilizar getcontext(), que como su nombre indica no es mas que un  contexto predeterminado local, que establece el hilo de ejecución  y que permite cambiar las propiedades en búsqueda de cambios en la precisión, el redondeo o captura de errores.

 Ahora cuando trabajes con un numero de coma flotante o con decimal, ya sabes un poco mas de que va el tema.

“Quien va en busca de montañas, no puede detenerse a recoger las piedras del camino”