Python por ejemplo
Tugurium/Python

Python, por ejemplo

Módulo fractions

2.4 Módulo fractions

A causa de las limitaciones en el almacenamiento del valor exacto de algunos números, el uso de operaciones con fracciones puede solventar esta situación, ofreciendo incuso el resultado como una fracción. Python dispone del módulo fractions, incluido en la biblioteca estándar desde la versión 2.6.

Con el módulo fractions podemos crear fracciones por diferentes vías y realizar operaciones matemáticas básicas o con ayuda del módulo math, además de establecer operaciones relacionales con las fracciones.

Para crear números racionales en forma de fracciones primero debemos importar el módulo fractions.

import fractions

Vamos a ver las funciones más importantes ofrecidas por este módulo. En la documentación de Python para fractions podéis encontrar todas las funciones disponibles.

2.4.1 Clases

Un objeto de la clase Fraction() puede construirse de varias maneras, a partir de un par de enteros, de otro número racional o de una cadena.

ClaseDescripción
fractions.Fraction(numerator=0, denominator=1)Crea una fracción con el valor racional numerator/denominator.
Por defecto el denominador tiene el valor 1.
Si el denominador es 0 se lanza una excepción ZeroDivisionError.
fractions.Fraction(other_fraction)Crea una fracción a partir de otra fracción
fractions.Fraction(float)Crea una fracción a partir de un número en coma flotante.
fractions.Fraction(decimal)Crea una fracción a partir de un número decimal.
fractions.Fraction(string)Crea una fracción a partir de una cadena con el formato:
[signo] numerador ['/' denominador]
El signo puede ser + o –.
El numerador y el denominador son cadenas de dígitos simples.

2.4.2 Métodos

El módulo fractions dispone de un método para limitar el denominador de la fracción.

MétodoDescripción
f.limit_denominator(max_denominator=1000000)Devuelve la fracción más cercana a sí misma que tiene el denominador con un valor como máximo el valor de max_denominator.
Este método es útil para encontrar aproximaciones racionales a un número de coma flotante dado.

2.4.3 Atributos

Podemos acceder al numerador y denominador de la fracción mediante los atributos de solo lectura correspondientes. Las fracciones son inmutables, así pues, no es posible modificar su valor.

AtributoDescripción
numerator Ofrece acceso al numerador de la fracción.
denominator Ofrece acceso al denominador de la fracción.

2.4.4 Uso del módulo fractions

Las fracciones en Python puro son mucho más lentas que los números de punto flotante, por eso, en muchos casos donde es más importante el rendimiento que la precisión, solo se hace uso de números en punto flotante. Pero, si lo deseamos, podemos hacer uso de las fracciones en Python y mezclarlas con otros números y operadores matemáticos en expresiones aritméticas.

Vamos a ver diferentes ejemplos sobre la creación de fracciones, operaciones a realizar con fracciones con los operadores aritméticos básicos y haciendo uso del módulo math.

2.4.4.1 Creación de fracciones

Mediante la clase Fraction() podemos crear una fracción en Python de diversas formas, haciendo uso de un argumento o de dos.

Con un solo argumento se convertirá el valor del dato en una fracción. En este caso podemos emplear números reales, números decimales obtenidos con el módulo decimal o incluso cadenas con el formato:

[<signo>]<numerador>['/'<denominador>]

No se admiten espacios al emplear esta notación.

Con dos argumentos estaremos pasando a la clase un numerador y un denominador para la fracción, que deben ser números enteros u otras fracciones. La fracción resultante se reduce automáticamente si es posible. No se puede crear una fracción con denominador cero, ya que la división por 0 es indefinida.

Cuando se imprime una fracción con print() Python la representa en el formato característico de numerador/denominador.

Para obtener valores aproximados a un número dado en notación decimal haremos uso del método limit_denominator().

Podemos redondear fracciones en Python de acuerdo con el número de dígitos decimales que se desee, utilizando la función integrada round(), que toma la fracción a redondear como primer argumento y el número de dígitos decimales que se quiere conservar como segundo argumento, en este caso siempre se obtendrá una fracción, incluso cuando se solicite cero dígitos. Cuando no pasamos segundo argumento, la función round() convierte la fracción al entero más cercano.

Vamos a ver lo expuesto anteriormente en el siguiente guión.

fraction_01_creacion.py
from fractions import Fraction
from decimal import Decimal

# crear fracción con numerador y denominador
print('(1)')
print('Numerador y denominador')
f = Fraction(7, 4)
print('Fraction(7, 4):', f)
print('Numerador:', f.numerator)
print('Denominador:', f.denominator)

# error denominador cero
print('\n(2)')
try:
    f = Fraction(7, 0)
except:
    print('ZeroDivisionError en Fraction(7, 0)')

print('\n(3)')
f1 = Fraction(7, -4)
print('Fraction(7, -4):', f1)

f2 = Fraction(8, 4)
print('Fraction(8, 4):', f2)

f3 = Fraction(f1, f2)
print('Fraction(Fraction(7, -4), Fraction(8, 4)):', f3)

# crear fracción con número en punto flotante
print('\n(4)')
print('Número en punto flotante')
f = Fraction(22.5)
print('Fraction(22.5):', f)

f = Fraction(11.5)
print('Fraction(11.5):', f)

f = Fraction(0.33)
print('Fraction(0.33):', f)

# creación a partir de un valor decimal
print('\nNúmero decimal')
f = Fraction(Decimal('0.33'))
print("Fraction(Decimal('0.33')):", f)

f = Fraction(Decimal('0.25'))
print("Fraction(Decimal('0.25')):", f)

# creación a partir de una cadena
print('\nCadena')
f = Fraction('14/8')
print("Fraction('14/8'):", f)

f = Fraction('-14/8')
print("Fraction('-14/8'):", f)

# obtención de un valor aproximado
print('\n(5)')
print('Aproximaciones')
f = Fraction('3.14159265358979323846')
print("Fraction('3.14159265358979323846'):\n", f)
f_1000 = f.limit_denominator(1000)
f_100 = f.limit_denominator(100)
f_10 = f.limit_denominator(10)
print (f'Limite 1000: {f_1000} = {float(f_1000)}')
print (f'Limite 100: {f_100} = {float(f_100)}')
print (f'Limite 10: {f_10} = {float(f_10)}')

# redondeo
print('\n(6)')
print('Redondeo')
f = Fraction('23/3')
print("Fraction('23/3'):", f)
round_limit = round(f, 2)
print('Redondeado con límite:', round_limit)
round_nolimit = round(f)
print('Redondeado sin límite:', round_nolimit)

El resultado de guión es:

(1)
Numerador y denominador
Fraction(7, 4): 7/4
Numerador: 7
Denominador: 4

(2)
ZeroDivisionError en Fraction(7, 0)

(3)
Fraction(7, -4): -7/4
Fraction(8, 4): 2
Fraction(Fraction(7, -4), Fraction(8, 4)): -7/8

(4)
Número en punto flotante
Fraction(22.5): 45/2
Fraction(11.5): 23/2
Fraction(0.33): 5944751508129055/18014398509481984

Número decimal
Fraction(Decimal('0.33')): 33/100
Fraction(Decimal('0.25')): 1/4

Cadena
Fraction('14/8'): 7/4
Fraction('-14/8'): -7/4

(5)
Aproximaciones
Fraction('3.14159265358979323846'):
 157079632679489661923/50000000000000000000
Limite 1000: 355/113 = 3.1415929203539825
Limite 100: 311/99 = 3.1414141414141414
Limite 10: 22/7 = 3.142857142857143

(6)
Redondeo
Fraction('23/3'): 23/3
Redondeado con límite: 767/100
Redondeado sin límite: 8

Comentemos el resultado.

  1. Empezamos creando fracciones con dos parámetros en la clase Fraction(), pasando numerador y denominador. Y vemos como obtenemos los valores independientes de numerador y denominador de la fracción con los atributos correspondientes.
  2. A continuación obtenemos una excepción, al intentar la creación de una fracción con denominador 0.
  3. Pasamos a crear una fracción con un valor negativo en el denominador, que se aplica a la fracción creada. Y una fracción cuyo resultado simplifica la fracción resultante de los parámetros que pasamos. Y creamos una fracción partiendo de las dos fracciones anteriores.
  4. Seguimos con la creación de fracciones a partir de parámetros en punto flotante, números decimales, y cadenas de caracteres que representan una fracción.
  5. Partiendo de la creación de una fracción, aplicamos sobre la misma la limitación al número de dígitos en el denominador con el método limit_denominator(), con diferentes valores, lo que da como resultado diferentes fracciones. La visualización de los resultados en punto flotante nos muestra ciertos problemas de precisión.
  6. Y terminamos con el uso de la función integrada round() para obtener diferentes fracciones (en el segundo caso un entero) con solo redondear al número de decimales indicado.

2.4.4.2 Operaciones con fracciones

Con las fracciones podemos utilizar las operaciones aritméticas básicas, que pueden estar compuestas por otros tipos numéricos, excepto por el tipo Decimal.

Debemos tener en cuenta que el tipo de datos del otro operando determinará el tipo del resultado de la operación aritmética. Cuando el operando es un número de punto flotante la fracción se convierte primero antes de realizar la operación, por lo que podemos obtener un tipo de datos diferente en el resultado.

El resultado de una suma ( + ) o resta ( - ) de fracciones es una nueva fracción, al igual que al sumar o restar enteros y fracciones.

En el caso de la multiplicación ( * ) ocurre lo mismo, dependiendo del tipo del otro operando obtendremos un tipo de dato diferente en el resultado.

En Python disponemos de dos operadores de división, el operador división ( / ) y división entera ( // ). La división entera devuelve un número entero, pero el resultado dependerá del tipo de dato que se utilice junto con la fracción.

También tenemos el operador módulo ( % ) y la función divmod(), que facilita la creación de fracciones mixtas.

Y podemos elevar una fracción a un valor con el operador exponenciación ( ** ) o con la función incorporada pow(). Como exponentes también podemos utilizar una fracción, aunque, si el exponente es una fracción, los operandos se suelen convertir en valores de punto flotante.

Terminar recordando que el tipo del dato determina el tipo del resultado si no se puede representar por una fracción.

En Python es posible realizar comparaciones entre fracciones usando los operadores relacionales habituales. Y se pueden ordenar utilizando la función incorporada sorted().

A continuación vamos a poner en práctica estas operaciones en el siguiente guión.

fraction_02_operaciones.py
from fractions import Fraction


# operaciones aritméticas con fracciones
print('Operaciones aritméticas')
a = Fraction(32, 5)
b = Fraction('14/8')
print(f'a: {a}, b: {b}')
# suma
c = a + b
print(f'Suma {a} + {b}:', c)
print(f'Suma {a} + {b} + 1:', c+1)
# resta
c = a - b
print(f'Resta {a} - {b}:', c)
print(f'Resta {a} - {b} - 2:', c-2)
print(f'Resta {a} - {b} - 0.5:', c-0.5)
# multiplicación
c = a * b
print(f'Multiplicación {a} * {b}:', c)
print(f'Multiplicación {a} * {b} * 2:', c*2)
# división
c = a / b
print(f'División {a} / {b}:', c)
print(f'División {a} / {b} / 2:', c/2)
# división entera
c = a // b
print(f'División entera {a} // {b}:', c)
# módulo
c = a % b
print(f'Módulo {a} % {b}:', c)
# fracción mixta
cociente, resto = divmod(c.numerator, c.denominator)
print(f'Fracción mixta:', cociente, Fraction(resto, c.denominator))
# potencia
c = a ** 4
print(f'Potencia {a} ** 4:', c)
c = a ** b
print(f'Potencia {a} ** {b}:', c)


# operadores relacionales
print('\nOperadores relacionales')
print('Fraction(16, 4) == Fraction(32, 8):', Fraction(16, 4) == Fraction(32, 8))
print('Fraction(15, 3) > Fraction(24, 6):', Fraction(15, 3) > Fraction(24, 6))
print('Fraction(36, 9) < Fraction(24, 4):', Fraction(36, 9) < Fraction(24, 4))
print('Fraction(36, 9) != Fraction(24, 4):', Fraction(36, 9) != Fraction(24, 4))
print('Fraction(1, 4) != 0.25:', Fraction(1, 4) != 0.25)

# ordenar series de fracciones
print('\nOrdenar series de fracciones')
serie = [Fraction(3, 5), Fraction(5, 11), Fraction(9, 16)]
print('Serie:', serie)
orden = sorted(serie)
print('Orden ascendente:', orden)
reverse = sorted(serie, reverse=True)
print('Orden descendente:', reverse)

En el resultado de la ejecución del guión podemos ver como las operaciones aritméticas con fracciones incluyen también números enteros o en punto flotante. Obtenemos también una fracción mixta haciendo uso de la función integrada divmod().

Comprobamos el uso de los operadores relacionales entre fracciones o con números en punto flotante.

Y por fin la comodidad de hacer uso de la función integrada sorted() para ordenar fracciones de forma ascendente o descendente.

Operaciones aritméticas
a: 32/5, b: 7/4
Suma 32/5 + 7/4: 163/20
Suma 32/5 + 7/4 + 1: 183/20
Resta 32/5 - 7/4: 93/20
Resta 32/5 - 7/4 - 2: 53/20
Resta 32/5 - 7/4 - 0.5: 4.15
Multiplicación 32/5 * 7/4: 56/5
Multiplicación 32/5 * 7/4 * 2: 112/5
División 32/5 / 7/4: 128/35
División 32/5 / 7/4 / 2: 64/35
División entera 32/5 // 7/4: 3
Módulo 32/5 % 7/4: 23/20
Fracción mixta: 1 3/20
Potencia 32/5 ** 4: 1048576/625
Potencia 32/5 ** 7/4: 25.75223663990652

Operadores relacionales
Fraction(16, 4) == Fraction(32, 8): True
Fraction(15, 3) > Fraction(24, 6): True
Fraction(36, 9) < Fraction(24, 4): True
Fraction(36, 9) != Fraction(24, 4): True
Fraction(1, 4) != 0.25: False

Ordenar series de fracciones
Serie: [Fraction(3, 5), Fraction(5, 11), Fraction(9, 16)]
Orden ascendente: [Fraction(5, 11), Fraction(9, 16), Fraction(3, 5)]
Orden descendente: [Fraction(3, 5), Fraction(9, 16), Fraction(5, 11)]

2.4.4.3 Fracciones con el módulo math

Se pueden usar funciones del módulo math para realizar operaciones más complejas con fracciones, ya que disponemos de la posibilidad de pasar fracciones como argumentos a las funciones del módulo math.

Vimos en el primer ejemplo un método de redondeo con la función integrada round(), vamos a emplear ahora otros sistemas que nos ofrece el módulo math. Con math.trunc(), redondeamos las fracciones positivas hacia abajo y las negativas hacia arriba, lo que podemos hacer también con los métodos math.floor() y math.ceil().

Vamos a ver un ejemplo donde se hace uso de una fracción como argumento o se crea una fracción a partir del resultado de una función del módulo math.

fraction_03_maths.py
from fractions import Fraction
import math 


# operaciones con fracciones y el módulo math
f = Fraction(25, 4)
print('Fraction(25, 4):', f)
# redondeo
print('Redondeo')
print ('truncar:', math.trunc(f)) 
print ('floor:', math.floor(f)) 
print ('floor:', math.floor(-f)) 
print ('ceil:', math.ceil(f)) 
print ('ceil:', math.ceil(-f)) 
print ('round:', round(f)) 
print('parte fraccionaria y entera:', math.modf(f))

# operciones diversas
print('\nFunciones exponenciales y logaritmicas')
print('potencia:', f*f, 'pow:', math.pow(f, 2))
print ('raíz:', math.sqrt(f))
print ('logaritmo:', math.log(f))
print ('logaritmo base 10:', math.log10(f))

# funciones trigonométricas
print('\nFunciones trigonométricas')
print ('pi:', Fraction(math.pi))
print ('pi con limitación:', Fraction(math.pi).limit_denominator(10000))
print ('seno(pi/3):', Fraction(math.sin(math.pi/3))) 
print ('seno(pi/3) con limitación:',
       Fraction(math.sin(math.pi/3)).limit_denominator(100))

En el resultado del script observamos el techo, suelo y redondeo de una fracción, así como sus parte entera y fraccionaria.

Calculamos potencias, raíces cuadradas y logaritmos haciendo uso de fracciones como argumentos.

Terminamos creando una fracción a partir del valor de pi del módulo math. Y calculamos el seno de pi/3 estableciendo diversos límites.

Fraction(25, 4): 25/4
Redondeo
truncar: 6
floor: 6
floor: -7
ceil: 7
ceil: -6
round: 6
parte fraccionaria y entera: (0.25, 6.0)

Funciones exponenciales y logaritmicas
potencia: 625/16 pow: 39.0625
raíz: 2.5
logaritmo: 1.8325814637483102
logaritmo base 10: 0.7958800173440752

Funciones trigonométricas
pi: 884279719003555/281474976710656
pi con limitación: 355/113
seno(pi/3): 3900231685776981/4503599627370496
seno(pi/3) con limitación: 84/97

Módulo fractions

    • Módulo fractions
      • Clases
      • Métodos
      • Atributos
      • Uso del módulo fractions
        • Creación de fracciones
        • Operaciones con fracciones
        • Fracciones con el módulo math