Модули Decimal и Fraction в Python
Из-за ограничений в сохранении точного значения чисел, даже простейшие математические операции могут выдавать ошибочный результат. Эти ограничения легко преодолимы — достаточно использовать десятичный модуль Decimal в Python. А в выполнении расчетов на основе дробей поможет модуль фракций — Fraction .
О принципах работы Decimal и Fraction и пойдет речь в данном обзоре.
Для чего нужен модуль Decimal?
Некоторые пользователи задаются вопросом, зачем нам нужен модуль для выполнения простейшей арифметики с десятичными числами, когда мы вполне можем сделать то же самое с помощью чисел с плавающей точкой 🤷♂️?
Перед тем, как мы ответим на данный вопрос, мы хотим, чтобы вы сами посчитали в Python, какой результат будет в данном примере: 0.1+0.2? Вы будете удивлены, когда узнаете, что правильный ответ — это не 0,3, а 0,30000000000000004.
Чтобы понять, почему в расчетах возникла ошибка, попробуйте представить 1/3 в десятичной форме. Тогда вы заметите, что число на самом деле не заканчивается в базе 10. Так как все числа должны быть каким-то образом представлены, при их сохранении в консоли делается несколько приближений, что и приводит к ошибкам.
Cпециально для читателей-гуманитариев, у нас есть объяснение принципов работы модулей Питона: «Она на долю секунды отвела взгляд» и «Она отвела взгляд на короткое время» — чувствуете разницу?
Чтобы получить точные результаты, подобные тем, к которым мы привыкли при выполнении расчетов вручную, нам нужно что-то, что поддерживает быструю, точно округленную, десятичную арифметику с плавающей запятой, и модуль Decimal отлично справляется с этой задачей. Теперь, когда мы разобрались с теорией, переходим к принципам работы десятичного модуля.
Модуль Decimal
Синтаксис
Decimal обеспечивает поддержку правильного округления десятичной арифметики с плавающей точкой.
>>> from decimal import Decimal >>> number1 = Decimal(«0.1») >>> number2 = Decimal(«0.7») >>> print(number1 + number2) 0.8
Decimal , в отличие от float , имеет ряд преимуществ:
- работает так же, как школьная арифметика;
- десятичные числа представлены точно (в отличие от float, где такие числа как 1.1 и 5.12 не имеют точного представления);
- точность десятичного модуля Decimal можно изменять (с помощью getcontext().prec );
Контекст
Базовые параметры Decimal можно посмотреть в его контексте, выполнив функцию getcontext() :
>>> from decimal import getcontext >>> getcontext() Context(prec=3, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
Точность
Контекстом в Decimal можно управлять, устанавливая свои значения. Например, для того, чтобы управлять точностью Decimal, необходимо изменить параметр контекста prec (от англ. precision — точность):
>>> from decimal import Decimal, getcontext >>> getcontext().prec = 2 >>> print(Decimal(‘4.34’) / 4) 1.1 >>> getcontext().prec = 3 >>> print(Decimal(‘4.34’) / 4) 1.08
Округление
Округление осуществляется с помощью метода quantize() . В качестве первого аргумента — объект Decimal, указывающий на формат округления:
>>> from decimal import Decimal >>> getcontext().prec = 4 # установим точность округление >>> number = Decimal(«2.1234123») >>> print(number.quantize(Decimal(‘1.000’))) 2.123 # округление до 3 чисел в дробной части >>> print(number.quantize(Decimal(‘1.00’))) 2.12 # округление до 2 чисел в дробной части >>> print(number.quantize(Decimal(‘1.0’))) 2.1 # округление до 1 числа в дробной части
💁♀️ Важно : если точность округления установлена в 2 , а формат округления Decimal(‘1.00’) , возникнет ошибка:
>>> print(number.quantize(Decimal(‘1.000’))) Traceback (most recent call last): File «», line 1, in print(number.quantize(Decimal(‘1.00’))) decimal.InvalidOperation: []
Чтобы избежать ее, необходимо поменять точность округления, как было сделано в примере выше:
>> getcontext().prec = 4 >>> print(number.quantize(Decimal(‘1.000’))) 2.123
Помимо первого параметра, quantize() принимает в качестве второго параметра стратегию округления:
- ROUND_CEILING — округление в направлении бесконечности (Infinity);
- ROUND_FLOOR — округляет в направлении минус бесконечности (- Infinity);
- ROUND_DOWN — округление в направлении нуля;
- ROUND_HALF_EVEN — округление до ближайшего четного числа. Число 4.9 округлится не до 5, а до 4 (потому что 5 — не четное);
- ROUND_HALF_DOWN — округление до ближайшего нуля;
- ROUND_UP — округление от нуля;
- ROUND_05UP — округление от нуля (если последняя цифра после округления до нуля была бы 0 или 5, в противном случае к нулю).
Помните, что как округление, так и точность вступают в игру только во время арифметических операций, а не при создании самих десятичных дробей
Полезные методы Decimal
Итак, вот некоторые полезные методы для работы с десятичными числами в Decimal:
- sqrt() — вычисляет квадратный корень из десятичного числа;
- exp() — возвращает e^x (показатель степени) десятичного числа;
- ln() — используется для вычисления натурального логарифма десятичного числа;
- log10() — используется для вычисления log (основание 10) десятичного числа;
- as_tuple() — возвращает десятичное число, содержащее 3 аргумента, знак (0 для +, 1 для -), цифры и значение экспоненты;
- fma(a, b) — «fma» означает сложить, умножить и добавить. Данный метод вычисляет (num * a) + b из чисел в аргументе. В этой функции округление num * a не выполняется;
- copy_sign() — печатает первый аргумент, копируя знак из второго аргумента.
📜 Полный список методов Decimal описан в официальной документации
Модуль Fraction
Этот модуль пригодится в тех случаях, когда вам необходимо выполнить вычисления с дробями, или когда результат должен быть выражен в формате дроби.
>>> from fractions import Fraction as frac >>> print(Fraction(33.33)) 2345390243441541/70368744177664 >>> print(Fraction(‘33.33’)) 3333/100
Модуль Fraction особенно полезен, потому что он автоматически уменьшает дробь. Выглядит это вот так:
>>> Fraction(153, 272) Fraction(9, 16)
Кроме того, вы можете выполнять бинарные (двоичные) операции над дробью также просто, как вы используете int или float . Просто добавьте две фракции:
>>> Fraction(1, 2) + Fraction(3, 4) Fraction(5, 4)
Теперь давайте попробуем возвести дробь в степень:
>>> Fraction(1, 8) ** Fraction(1, 2) 0.3535533905932738
Когда использовать Decimal и Fraction?
Потребность в максимальной точности расчетов на практике чаще всего возникает в отраслях и ситуациях, где некорректно выбранная точность расчетов может обернуться серьезными финансовыми потерями:
- Обмен валют . Особенно если этот процесс подразумевает не просто конвертацию евро в рубли, тенге или иную валюту, а выполнение более сложных операций.
- Масштабируемые расчеты . К примеру, на фабрике начинают готовить печенье по бабушкиному рецепту, в котором упоминается «1/3 столовой ложки» определенного ингредиента. Сколько именно литров или миллилитров составит эта треть, если применять ее не к одной порции печенья, а к промышленным масштабам? А сколько это составит в пересчете на «неродную» систему мер, то есть фунтов или унций?
- Работа с иррациональными числами . Если вы планируете запускать спутник или возводить энергетическую станцию, точность расчетов необходимо задать еще до того, как вы приступите к самым первым вычислениям. И оповестить об этом всех, кто имеет хоть какое-то отношение к проекту.
Таким образом, этих двух модулей должно быть достаточно, чтобы помочь вам выполнять общие операции как с десятичными, так и с дробными числами. Как мы уже говорили, вы можете использовать эти модули вместе с математическим модулем для вычисления значения всех видов математических функций в желаемом формате.
Модуль Decimal незаменим, если нужно считать деньги: с его помощью вы сможете подсчитать точную сумму, вплоть до копеек.
Fraction считает просто и честно: любители онлайн-игр приспособили его для подсчетов в игровой математике.