Класс Fraction модуля fractions

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

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

на уровне объектов класса Decimal не выдаст точного значения один:

res = Decimal('1') / Decimal('3') * Decimal('3')
print(res)  # 0.9999999999999999999999999999

во многом из-за невозможности точно описать дробь 1/3 в виде вещественного значения (конечной десятичной дробью).

Для решения подобных задач был предложен специальный класс Fraction, который способен описывать любые обыкновенные дроби без потери информации. Например, злополучную дробь 1/3 можно определить следующим образом:

from fractions import Fraction
 
a = Fraction(1, 3)
b = Fraction('1/3')
c = Fraction(3, 9)
d = Fraction('5/15')

Во всех случаях будет создан объект класса Fraction для представления дроби 1/3. То есть, здесь числитель и знаменатель дроби хранятся отдельно в виде целых чисел. Обратите внимание, что две последние дроби 3/9 = 5/15 = 1/3 сокращаются до дроби 1/3. И это всегда так. Класс Fraction содержит наименьшие (по модулю) целые значения числителя и знаменателя.

Теперь, если умножить какое-либо из этих чисел на три:

res = a * 3  # Fraction(1, 1)

то получим объект класса Fraction с дробью 1/1 = 1. В результате, благодаря использованию обыкновенных дробей вместо вещественных чисел, имеем возможность точного вычисления итогового результата. В этом главная особенность и преимущество чисел класса Fraction. При этом каждый такой объект содержит два полезных свойства для получения значений числителя и знаменателя:

b.numerator  # 1 – числитель
b.denominator  # 3 – знаменатель

Либо можно воспользоваться методом as_integer_ratio() для извлечения тех же данных в виде кортежа:

b.as_integer_ratio()  # (1, 3)

Зная эти величины, всегда можно число типа Fraction перевести в число типа float или Decimal. Или использовать стандартные функции приведения типов:

bf = float(b)  # 0.3333333333333333
bi = int(b)  # 0 (выделение целой части)
bs = str(b)  # '1/3'
bool(b)  # True
b0 = Fraction(0)
bool(b0)  # False

Как видите, с объектами класса Fraction возможны любые стандартные преобразования.

Создание объектов класса Fraction

Давайте теперь посмотрим на способы создания объектов (чисел) класса Fraction. В общем случае это можно делать так:

a1 = Fraction(5, 6)  # Fraction(5, 6)
a2 = Fraction(3, 6)  # Fraction(1, 2)
a3 = Fraction(-3, 6)  # Fraction(-1, 2)
a4 = Fraction(-3, -6)  # Fraction(1, 2)
a5 = Fraction(5)  # Fraction(5, 1)
a6 = Fraction(1.5)  # Fraction(3, 2)
a7 = Fraction('1/2')  # Fraction(1, 2)
a8 = Fraction('0.756')  # Fraction(189, 250)
a9 = Fraction('-2.3')  # Fraction(-23, 10)
a10 = Fraction('2e-3')  # Fraction(1, 500)

То есть, можно прописывать или два целых числа (числитель, знаменатель), или одно целое число (числитель), тогда знаменатель будет равен 1, или вещественное значение, которое будет преобразовано в обыкновенную дробь, или передавать строки с числами в разных форматах. Также мы можем создавать числа Fraction на основе чисел Decimal следующим образом:

d = Decimal('5.47')
a11 = Fraction(d)  # Fraction(547, 100)

Все это вполне допустимые операции.

Обратите внимание, что вещественные числа лучше явно не прописывать при создании объектов Fraction. Например, команда:

Fraction(0.1)  # Fraction(3602879701896397, 36028797018963968)

создаст очень странную дробь. И все из-за неточного представления числа 0.1 в памяти компьютера. А вот если эту же команду прописать с использованием строкового значения числа:

Fraction('0.1')  # Fraction(1, 10)

то получим вполне ожидаемый результат. Поэтому вместо вещественных чисел старайтесь всегда использовать их строковые аналоги.

Арифметические операции с числами класса Fraction

Обыкновенные дроби, представленные классом Fraction, можно складывать, вычитать, умножать и делить. Делаются все эти операции согласно общим правилам математики. Рассмотрим их на простых примерах.

Пусть имеются две дроби 1/2 и 1/3:

from fractions import Fraction
 
a = Fraction(1, 2)
b = Fraction('1/3')

После их сложения, получим:

c = a + b  # Fraction(5, 6)

Действительно, согласно школьной математике, имеем:

Обратите внимание, что дроби в классе Fraction всегда автоматически приводятся к наименьшему знаменателю. Например, сложение следующих двух дробей:

a = Fraction('3/4')
b = Fraction('4/8')  
c = a + b  # Fraction(5, 4)

в действительности описывает такое сложение:

Несмотря на то, что дробь b определена как 4/8, она автоматически сокращается до 1/2 (представляется с наименьшим знаменателем).

Операция вычитания работает по аналогии, т.к. это по сути сложение положительной и отрицательной дробей. Например:

a = Fraction('3/4')
b = Fraction('7/8')
c = a - b  # Fraction(-1, 8)
d = b - a  # Fraction(1, 8)

Следующая операция умножения дробей записывается в виде:

a = Fraction('3/4')
b = Fraction('1/3')
c = a * b  # Fraction(1, 4)

и вычисляется следующим образом:

Наконец, деление записывается так:

a = Fraction('3/4')
b = Fraction('2/3')
c = a / b  # Fraction(9, 8)
d = b / a  # Fraction(8, 9)

Согласно правилам математики эти дроби вычисляются следующим образом:

Помимо базовых арифметических операций с числами класса Fraction можно выполнять и некоторые дополнительные:

a ** 2  # Fraction(9, 16)
a // b  # 1
a +=# Fraction(17, 12)
b *= 5  # Fraction(10, 3)
a - 7  # Fraction(-67, 12)

Обратите внимание, что объекты класса Fraction можно вычислять совместно с целыми числами. А вот с дробными float дроби преобразуются в конечные десятичные и результат получается в виде десятичных дробей:

b + 0.5  # 3.8333333333333335

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

Операции сравнения

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

a = Fraction('0.5')
b = Fraction('2/4')
c = Fraction('3/4')

Тогда операции сравнения с ними вернут следующие результаты:

a ==# True
a ==# False
b !=# True
a <# True
a <=# True
c > a + b  # False
c >=# True

Обратите внимание, что дроби a и b равны между собой, т.к. это одна и та же дробь 1/2. Результат работы всех остальных операций вполне очевиден.

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме