Стандартные функции с объектами класса Fraction

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

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

Вначале рассмотрим еще один способ формирования объектов Fraction на основе значений типа float. Например, нам требуется приближенно описать число пи:

pi = math.pi  # 3.141592653589793

с помощью класса Fraction. Если просто записать команду:

pf = Fraction(pi)  # Fraction(884279719003555, 281474976710656)

то получим большие значения в числителе и знаменателе. Конечно, они наилучшим образом описывают заданное значение pi. Но иметь такие величины в итоговых результатах вычислений не всегда удобно. Поэтому можно задать ограничение максимального значения знаменателя, получить более информативную дробь, но с менее точным значением исходного вещественного числа. Задается это ограничение с помощью метода limit_denominator() следующим образом:

pf = Fraction(pi)  # Fraction(884279719003555, 281474976710656)
pf2 = pf.limit_denominator()  # Fraction(3126535, 995207)

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

float(pf2) # 3.1415926535886505
pi         # 3.141592653589793

Как видите, значения немного различаются, но дробь стала короче. Давайте еще больше ее сократим, например, так:

pf2 = pf.limit_denominator(10)  # Fraction(22, 7)

Имеем довольно аккуратную дробь (знаменатель не превышает значения 10), но с еще менее точным значением числа пи:

float(pf2)  # 3.142857142857143
pi          # 3.141592653589793

Вот так можно делать различные аппроксимации вещественных чисел обыкновенными дробями.

Использование функций с объектами Fraction

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

a = Fraction('5/2')
b = Fraction(19, 123)
c = Fraction('-7/2')

Тогда функция round() возвратит значения:

round(a) # 2
round(b, 2) # Fraction(3, 20)
round(b, 3) # Fraction(77, 500)

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

b = 19/123 = 0.15447154471544716

Округляя по правилам школьной математики это значение, имеем:

round(b, 2) # 0.15 = 3/20
round(b, 3) # 0.154 = 77/500

Это поведение заложено в магическом методе __round__() класса Fraction. По аналогии в методе __abs__() описан алгоритм вычисления модуля дробей. Например:

abs(c)  # Fraction(7, 2)

Помимо этого с объектами класса Fraction можно использовать функции стандартного модуля math:

math.trunc(a)  # 2
math.floor(a)  # 2
math.ceil(a)  # 3

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

math.sqrt(a)  # 1.5811388300841898
math.sin(a)  # 0.5984721441039564

Во всех этих функциях объекты Fraction сначала преобразуются к вещественному типу float:

float(a)  # 2.5

и только потом, происходят вычисления.

Помимо функций round(), abs(), trunk(), floor(), ceil() с объектами класса Fraction можно использовать и некоторые другие встроенные функции языка Python. Например, пусть имеется следующий набор дробей:

lst = [Fraction(x, y) for x, y in zip((1, 5, -7, 11, -13, 19), (2, 4, 8, 6, 3, 20))]

Здесь формируется список из объектов:

[Fraction(1, 2), Fraction(5, 4), Fraction(-7, 8), Fraction(11, 6), Fraction(-13, 3), Fraction(19, 20)]

С этим списком можно выполнять следующие операции:

sum(lst)  # Fraction(-27, 40)
tuple(filter(lambda x: x.numerator < 10, lst))  # (Fraction(1, 2), Fraction(5, 4), Fraction(-7, 8), Fraction(-13, 3))
list(map(abs, lst))  # [Fraction(1, 2), Fraction(5, 4), Fraction(7, 8), Fraction(11, 6), Fraction(13, 3), Fraction(19, 20)]
sorted(lst) # [Fraction(-13, 3), Fraction(-7, 8), Fraction(1, 2), Fraction(19, 20), Fraction(5, 4), Fraction(11, 6)]
max(lst)  # Fraction(11, 6)
min(lst)  # Fraction(-13, 3)

Пример нахождения корней квадратного уравнения

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

from fractions import Fraction
import math
 
a, b, c = map(int, input().split())
 
D = b * b - 4 * a * c
 
if D > 0:
    sqrt_D = Fraction(math.sqrt(D)).limit_denominator()
    root1 = (-b + sqrt_D) / (2 * a)
    root2 = (-b - sqrt_D) / (2 * a)
    result = f"Квадратное уравнение имеет два корня:\nx1 = {root1}\nx2 = {root2}"
elif D == 0:
    root = Fraction(-b, (2 * a))
    result = f"Квадратное уравнение имеет один корень:\nx = {root}"
else:
    result = "Квадратное уравнение не имеет действительных корней."
 
print(result)

Вначале вводятся три целых числа a, b, c – коэффициенты квадратного уравнения. Затем, вычисляется дискриминант и проверяется на больше, равно и меньше нуля. Если D > 0, то имеем два действительных корня. Первым делом нужно вычислить квадратный корень из дискриминанта. Для этого используем стандартную функцию math.sqrt(), затем на основе вещественного значения формируем дробь с ограничением по знаменателю (чтобы дробь не получалась слишком большой). В итоге переменная sqrt_D ссылается на объект класса Fraction и дальнейшие целочисленные операции гарантированно будут давать объекты этого класса.

Если же дискриминант равен нулю, то вычисления становятся гораздо проще. Здесь можно сразу сформировать дробь, как результат вычислений. В последнем варианте, когда D < 0, формируем строку с сообщением отсутствия действительных корней.

Если после запуска программы ввести значения:

3 5 2

то на выходе получим корни в виде дробей:

x1 = -2/3
x2 = -1

Обратите внимание, что значение -2/3 не может быть представлено конечной десятичной дробью. И если бы вычисления выполнялись на уровне типа float, то результатом были бы числа:

x1 = -0.6666666666666666
x2 = -1.0

С математической точки зрения это менее информативный результат. Но благодаря применению класса Fraction мы получаем вполне понятные обыкновенные дроби.

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

Видео по теме