fractions - рациональные числа (обыкновенные дроби)

Модуль fractions позволяет выполнять арифметические действия над рациональными числами, что в некоторых ситуациях бывает чрезвычайно удобно.

Создание обыкновенных дробей

Способов создания экземпляров рациональных чисел, довольно много поэтому мы разделим их на группы.

Самый простой способ создать обыкновенную дробь – указать числитель (numerator) и знаменатель (denominator):

>>> from fractions import Fraction
>>> 
>>> Fraction()    #  по умолчанию numerator=0, denominator=1
Fraction(0, 1)
>>> 
>>> Fraction(numerator=1, denominator=2)    #  равносильно Fraction(1, 2)
Fraction(1, 2)
>>> 
>>> Fraction(1, 2)
Fraction(1, 2)

Если указанные числитель и знаменатель имеют общие делители, то перед созданием рационального числа они будут сокращены:

>>> Fraction(8, 16), Fraction(15, 30)
(Fraction(1, 2), Fraction(1, 2))

В качестве числителя и (или) знаменателя могут быть указаны другие дроби:

>>> Fraction(3, Fraction(1, 2))
Fraction(6, 1)
>>> 
>>> Fraction(Fraction(1, 2), 3)
Fraction(1, 6)
>>> 
>>> Fraction(Fraction(1, 2), Fraction(3, 7))
Fraction(7, 6)

Целое и вещественное число, так же можно преобразовать в обыкновенную дробь:

>>> Fraction(10)
Fraction(10, 1)
>>> 
>>> Fraction(11.11)
Fraction(781796747813847, 70368744177664)
>>> 
>>> Fraction(1.25)
Fraction(5, 4)
>>> 
>>> Fraction(2e-10)
Fraction(7737125245533627, 38685626227668133590597632)

А вот комплексные числа приведут к ошибке:

>>> Fraction(1j, 1 + 2j)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: both arguments should be Rational instances

Но мнимой и действительной частью комплексного числа, могут быть указаны рациональные числа:

>>> complex(Fraction(3, 5), Fraction(4, 7))
(0.6+0.5714285714285714j)

Так же любая десятичная дробь модуля Decimal может быть преобразована в обыкновенную дробь:

>>> from decimal import Decimal
>>> Fraction(Decimal('7.7'))
Fraction(77, 10)

Ну и напоследок, любая строка, которая может быть преобразована в любое допустимое число, так же может быть использована для получения обыкновенных дробей:

>>> Fraction('111')
Fraction(111, 1)
>>> 
>>> Fraction('1.11')
Fraction(111, 100)
>>> 
>>> Fraction('1.11e+10')
Fraction(11100000000, 1)
>>> 
>>> Fraction('-17/63')
Fraction(-17, 63)
>>> 
>>> Fraction('-0.115')
Fraction(-23, 200)
>>> 
>>> Fraction('-.115')
Fraction(-23, 200)
>>> 
>>> Fraction('1.11 \t\n')
Fraction(111, 100)
>>> Fraction('\t\n 1.11 \t\n')
Fraction(111, 100)
>>> Fraction('\t\n 1.11')
Fraction(111, 100)
>>> 
>>> Fraction('\t\n -13/37')
Fraction(-13, 37)

Математические операции над рациональными числами

Все арифметические операторы поддерживают вычисления с рациональными числами:

>>> x = Fraction(2, 5)
>>> y = Fraction(3, 7)
>>> 
>>> x + y, y - x
(Fraction(29, 35), Fraction(1, 35))
>>> 
>>> x*y, x/y
(Fraction(6, 35), Fraction(14, 15))
>>> 
>>> x**0.5, 2**0.5/5**0.5
(0.6324555320336759, 0.6324555320336759)
>>> 
>>> x**y, (x**3)**(1/7)
(0.6752339686501552, 0.6752339686501552)
>>> 
>>> y//x
1
>>> x%y
Fraction(2, 5)

Однако арифметические операторы не способны к действиям над типами 'Fraction' и 'decimal.Decimal' в одном выражении:

>>> x + 1.1
1.5
>>> 
>>> x + Decimal('1.1')    #  приведет к ошибке
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Fraction' and 'decimal.Decimal'

Функции модуля math способны принимать рациональные числа в качестве аргументов, так как последние, по сути, могут быть просто преобразованы к вещественным числам:

>>> import math
>>> 
>>> x = Fraction(4, 11)
>>> 
>>> math.exp(x), math.exp(4/11)
(1.438551009577678, 1.438551009577678)

Fraction — aтрибуты и методы

x.numerator
возвращает числитель дроби.
>>> from fractions import Fraction
>>> 
>>> x = Fraction(3, 8)
>>> x
Fraction(3, 8)
>>> 
>>> x.numerator
3
x.denominator
возвращает знаменатель дроби.
>>> x.denominator
8
Fraction.from_float(flt)
принимает flt – число типа float и возвращает обыкновенную дробь отношение числителя к знаменателю которой максимально приближается к значению flt.
>>> from fractions import Fraction
>>> 
>>> Fraction.from_float(0.5)
Fraction(1, 2)
>>> 
>>> Fraction.from_float(0.6)
Fraction(5404319552844595, 9007199254740992)

Как можно заметить, из-за хранения чисел с плавающей точкой в двоичном представлении Fraction.from_float(0.6) вовсе не равно Fraction(6, 10):

>>> Fraction.from_float(0.6) == Fraction(6, 10)
False
Fraction.from_decimal(dec)
создает обыкновенную дробь, которая является точным представлением десятичной дроби указанной в dec, где dec – это экземпляр класса decimal.Decimal.
>>> from decimal import Decimal
>>> 
>>> Fraction.from_decimal(Decimal('0.7'))
Fraction(7, 10)
>>> 
>>> Fraction.from_decimal(Decimal('0.77'))
Fraction(77, 100)
>>> 
>>> Fraction.from_decimal(Decimal('0.125'))
Fraction(1, 8)
Fraction.limit_denominator(max_denominator=1000000)
возвращает ближайшее рациональное представление указанного числа, знаменатель которого не превышает значение max_denominator.
>>> import math
>>> math.pi
3.141592653589793
>>> 
>>> Fraction(math.pi).limit_denominator(10)
Fraction(22, 7)
>>> 
>>> Fraction(math.pi).limit_denominator(1000)
Fraction(355, 113)
>>> 
>>> Fraction(math.pi).limit_denominator(100000)
Fraction(312689, 99532)

Данный метод позволяет получать как рациональные приближения, так и восстанавливать рациональные значения по их неточному представлению в виде чисел с плавающей точкой:

>>> math.cos(math.pi/3)     #  точное значение 0.5
0.5000000000000001
>>> 
>>> Fraction(math.cos(math.pi/3)).limit_denominator()
Fraction(1, 2)
>>> 
>>> 
>>> 2**(1/3)
1.2599210498948732
>>> 
>>> Fraction(2**(1/3)).limit_denominator()
Fraction(1054215, 836731)
>>> 
>>> 1054215/836731
1.2599210498953666
x.__floor__()
возвращает значение типа int, которое является наибольшим среди всех int <= x.
>>> Fraction(234, 47).__floor__()
4

Ну и учитывая что, все функции модуля math могут обрабатывать обыкновенные дроби, то воспользоваться данным методом можно и так:

>>> math.floor(Fraction(234, 47))
4
x.__ceil__()
возвращает значение типа int, которое является наименьшим среди всех int >= x.
>>> Fraction(234, 47).__ceil__()
5
>>> 
>>> import math
>>> 
>>> math.ceil(Fraction(234, 47))
5
x.__round__(ndigits=None)
Округляет до ближайшего четного числа.

Если ndigits=None, то округление выполняется до ближайшего четного целого числа:

>>> Fraction('1/2').__round__()
0
>>> 
>>> Fraction('3/2').__round__()
2

Если ndigits не равно None, то округление выполняется до ближайшего числа кратного дроби Fraction(1, 10**ndigits), при этом если последняя цифра \(5\), то округление будет выполнено к ближайшему четной цифре:

>>> Fraction('7/3').__round__(2)
Fraction(233, 100)
>>> 
>>> Fraction('7/3').__round__(3)
Fraction(2333, 1000)
>>> 
>>> 
>>> Fraction('555/1000').__round__(1)
Fraction(3, 5)
>>> 
>>> Fraction('555/1000').__round__(2)
Fraction(14, 25)

Если ndigits не равно None, но меньше 0, то округление выполняется до разряда на который указывает abs(ndigits), при этом если последняя цифра \(5\), то округление будет выполнено к ближайшей четной цифре

>>> Fraction('-5555555/10').__round__(-1)
Fraction(-555560, 1)
>>> Fraction('-5555555/10').__round__(-3)
Fraction(-556000, 1)
>>> 
>>> Fraction('77777/10').__round__(-3)
Fraction(8000, 1)
fractions.gcd(a, b)
возвращает наибольший общий делитель чисел a и b.

Возвращает НОД(a, b). Знак результата зависит от знака b, если b не равен \(0\), в противном случае знак результата равен знаку a. Возвращает \(0\), только если a и b оба равны \(0\):

>>> fractions.gcd(135, 15)
15
>>> fractions.gcd(135, -15)
-15
>>> fractions.gcd(-135, 15)
15
>>> fractions.gcd(-135, 0)
-135
>>> fractions.gcd(0, 0)
0

С версии 3.5. считается устаревшим, и предпочтительнее использовать команду math.gcd().