Incluso las operaciones matemáticas más básicas a veces pueden darte un resultado erróneo. Esto ocurre debido a limitaciones en el almacenamiento del valor exacto de algunos números. Puede superar estas limitaciones utilizando el módulo decimal en Python. Del mismo modo, ni el módulo math, ni el módulo cmath, que vimos en el anterior tutorial pueden ayudarnos a realizar operaciones aritméticas basadas en fracciones. Sin embargo, el módulo fractions de Python puede ayudarte con este asunto.
En este tutorial, aprenderás sobre estos módulos y las diferentes funciones disponibles.
¿Por qué necesitamos un módulo decimal?
Probablemente te estés preguntando por qué necesitas un módulo para realizar operaciones aritméticas básicas con números decimales cuando podemos hacerlo utilizando floats.
Antes de responder a esta pregunta, quiero que adivines el valor de salida si escribes 0.1 + 0.2 en la consola de Python. Seguro que piensas que el valor de salida será 0.3, pero si lo compruebas verás que el valor resultante es otro totalmente distinto, es 0.30000000000000004. Puedes probar otra operación como 0.05 + 0.1, y obtendrás como resultado 0.15000000000000002.
Para entender lo que está pasando aquí, trata de representar 1/3 en forma decimal, y verás que el número realmente no está en base 10. Del mismo modo, algunos números como 0,1 o 1/10 no están en base 2. Ya que estos números deben ser representados de alguna manera, se hacen aproximaciones mientras se almacenan, lo que da lugar a esos errores.
El número 0.30000000000000004 está muy cerca de 0.3, por lo que esta aproximación nos puede servir en la mayoría de casos. Por desgracia, esta aproximación no nos va servir para, por ejemplo, simular el lanzamiento de un satélite o en temas financieros.
Para obtener resultados precisos como los que estamos acostumbrados a tratar al hacer cálculos a mano, necesitamos algo que soporte aritmética decimal, redondeada correctamente, y el módulo decimal te ayudará precísamente a eso.
Uso del módulo Decimal
Antes de utilizar el módulo, primero debes importarlo. Después de eso, puedes crear decimales de números enteros, strings, floats o tuplas. Cuando el decimal se construye a partir de un entero o un float, se hace una conversión exacta del valor de dicho número. Echa un vistazo a los siguientes ejemplos para que veas lo que quiero decir:
from decimal import Decimal Decimal(121) # returns Decimal('121') Decimal(0.05) # returns Decimal('0.05000000000000000277555756') Decimal('0.05') # returns Decimal('0.05') Decimal((0, (8, 3, 2, 4), -3)) # returns Decimal('8.324') Decimal((1, (8, 3, 2, 4), -1)) # returns Decimal('-832.4')
Como puedes ver, el valor de Decimal(0.05) es algo diferente de Decimal ('0.05'). Esto significa que cuando añadas 0.05 y 0.1, debes utilizar decimal.Decimal('0.05') y decimal.Decimal('0.1') al construir los decimales.
from decimal import Decimal Decimal('0.05') + Decimal('0.1') # returns Decimal('0.15') Decimal(0.05) + Decimal(0.1) # returns Decimal('0.1500000000000000083266726847')
Ahora que puedes realizar varias operaciones con decimales, es posible que desees controlar la precisión o el redondeo de dichas operaciones. Esto puede hacerse utilizando la función getcontext (). Esta función te permite obtener, así como establecer, el valor de la precisión y las opciones de redondeo, entre otras cosas.
Ten en cuenta que tanto el redondeo, como la precisión, entran en juego sólo durante las operaciones aritméticas y no durante la creación de los decimales.
import decimal from decimal import Decimal, getcontext Decimal(1) / Decimal(13) # returns Decimal('0.07692307692307692307692307692') getcontext().prec = 10 Decimal(0.03) # returns Decimal('0.02999999999999999888977697537') Decimal(1) / Decimal(7) # returns Decimal('0.1428571429') getcontext().rounding = decimal.ROUND_DOWN Decimal(1) / Decimal(7) # returns Decimal('0.1428571428')
También puedes utilizar funciones matemáticas como sqrt(), exp() y log() con decimales. Aquí tienes unos cuantos ejemplos:
import decimal from decimal import Decimal, getcontext Decimal(2).sqrt() # returns Decimal('1.414213562373095048801688724') getcontext().prec = 4 Decimal('2').sqrt() # returns Decimal('1.414') Decimal('2000').log10() # returns Decimal('3.301')
Uso del módulo Fractions
A veces, te enfrentas a situaciones en las que necesitas realizar operaciones con fracciones o que el resultado final debe ser una fracción. El módulo fractions puede ser de gran ayuda en estos casos. Te permite crear una instancia Fraction de números, floats, decimales e incluso strings. Al igual que el módulo decimal, hay ciertos problemas con este módulo a la hora de crear fracciones de floats. Aquí tienes unos cuantos ejemplos:
from fractions import Fraction from decimal import Decimal Fraction(11, 35) # returns Fraction(11, 35) Fraction(10, 18) # returns Fraction(5, 9) Fraction('8/25') # returns Fraction(8, 25) Fraction(1.13) # returns Fraction(1272266894732165, 1125899906842624) Fraction('1.13') # returns Fraction(113, 100) Fraction(Decimal('1.13')) # returns Fraction(113, 100)
También puede realizar operaciones matemáticas simples como sumar y restar con fracciones, del mismo modo que con números normales.
from fractions import Fraction Fraction(113, 100) + Fraction(25, 18) # returns Fraction(2267, 900) Fraction(18, 5) / Fraction(18, 10) # returns Fraction(2, 1) Fraction(18, 5) * Fraction(16, 19) # returns Fraction(288, 95) Fraction(18, 5) * Fraction(15, 36) # returns Fraction(3, 2) Fraction(12, 5) ** Fraction(12, 10) # returns 2.8592589556010197
El módulo también cuenta con métodos importantes como limit_denominator(max_denominator) que encontrará y devolverá la fracción más cercana en valor a la fracción dada cuyo denominador es como máximo max_denominator. También puedes devolver el numerador de una fracción dada en el término más bajo utilizando la propiedad numerator y el denominador utilizando la propiedad denominator.
from fractions import Fraction Fraction('3.14159265358979323846') # returns Fraction(157079632679489661923, 50000000000000000000) Fraction('3.14159265358979323846').limit_denominator(10000) # returns Fraction(355, 113) Fraction('3.14159265358979323846').limit_denominator(100) # returns Fraction(311, 99) Fraction('3.14159265358979323846').limit_denominator(10) # returns Fraction(22, 7) Fraction(125, 50).numerator # returns 5 Fraction(125, 50).denominator # returns 2
También puedes utilizar este módulo con varias funciones del módulo math.
import math from fractions import Fraction math.sqrt(Fraction(25, 4)) # returns 2.5 math.sqrt(Fraction(28,3)) # returns 3.0550504633038935 math.floor(Fraction(3558, 1213)) # returns 2 Fraction(math.sin(math.pi/3)) # returns Fraction(3900231685776981, 4503599627370496) Fraction(math.sin(math.pi/3)).limit_denominator(10) # returns Fraction(6, 7)
Fuente: Monty Shokeen