El módulo fractions nos permite crear y trabajar con rational numbers: fracciones con un numerador entero dividido entre un denominador entero.
Por ejemplo, podemos guardar 2/3 como una fracción exacta en lugar del valor float aproximado 0.6666...
A diferencia de los números int, float y complex, las fracciones no tienen una forma literal.
Sin embargo, el constructor de fracciones es bastante flexible.
Lo más evidente es que puede recibir dos enteros. Los factores comunes se eliminan automáticamente, lo que convierte la fracción a su «mínima expresión» (los enteros más pequeños que representan la fracción con exactitud):
>>> from fractions import Fraction
>>> f1 = Fraction(2, 3) # 2/3
>>> f1
Fraction(2, 3)
>>> f2 = Fraction(6, 9)
>>> f2
Fraction(2, 3) # automatically simplified
>>> f1 == f2
True
El constructor de fracciones también puede interpretar una representación como string:
>>> f3 = Fraction('2/3')
>>> f3
Fraction(2, 3)
También puede trabajar con parámetros float, pero esto puede dar problemas por la naturaleza aproximada de representar el valor decimal internamente en binario.
Para saber más sobre este problema de representación, consulta el sitio web 0.30000000000000004 y Floating Point Arithmetic: Issues and Limitations en la documentación de Python.
Para obtener un resultado más confiable al usar números de punto flotante con fracciones, existe el método <fraction>.limit_denominator().
.limit_denominator() puede recibir un parámetro entero si tienes requisitos específicos, pero incluso el valor predeterminado (max_denominator=1000000) puede funcionar bien y dar una aproximación simple y aceptable.
>>> Fraction(1.2)
Fraction(5404319552844595, 4503599627370496)
>>> Fraction(1.2).limit_denominator()
Fraction(6, 5)
Los arithmetic operators habituales + - * / ** funcionan con fracciones, igual que con otros tipos numéricos.
Se pueden incluir enteros y otros Fraction, y el resultado será un Fraction.
Si incluyes un float en la expresión, el resultado será un float, con la consiguiente (posible) pérdida de precisión.
>>> Fraction(2, 3) + Fraction(1, 4) # addition
Fraction(11, 12)
>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions
Fraction(4, 5)
>>> Fraction(2, 3) * 6 / 5 # fraction with integers
Fraction(4, 5)
>>> Fraction(2, 3) * 1.2 # fraction with float -> float
0.7999999999999999
>>> Fraction(2, 3) ** 2 # exponentiation with integer
Fraction(4, 9)
Las fracciones son excelentes para conservar la precisión durante los cálculos intermedios, pero puede que no sean lo que quieres para el resultado final.
Es posible obtener el numerador y el denominador por separado o como una tupla (tuples se verá en un concepto posterior):
>>> Fraction(2, 3).numerator
2
>>> Fraction(2, 3).denominator
3
>>> Fraction(2, 3).as_integer_ratio()
(2, 3)
Varias funciones numéricas estándar de Python también dan el resultado que esperarías al trabajar con los tipos int y float:
>>> round(Fraction(11, 3))
4
>>> from math import floor, ceil
>>> floor(Fraction(11, 3))
3
>>> ceil(Fraction(11, 3))
4
>>> float(Fraction(11, 3))
3.6666666666666665