Модули Decimal и Fraction
upd:
7.4K
2

Модули Decimal и Fraction в Python

  • Для чего нужен модуль Decimal?
  • Модуль Decimal
  • Синтаксис
  • Контекст
  • Точность
  • Округление
  • Полезные методы Decimal
  • Модуль Fraction
  • Когда использовать Decimal и Fraction?

Из-за ограничений в сохранении точного значения чисел, даже простейшие математические операции могут выдавать ошибочный результат. Эти ограничения легко преодолимы — достаточно использовать десятичный модуль Decimal в Python. А в выполнении расчетов на основе дробей поможет модуль фракций — Fraction.

О принципах работы Decimal и Fraction и пойдет речь в данном обзоре.

Для чего нужен модуль Decimal?

Некоторые пользователи задаются вопросом, зачем нам нужен модуль для выполнения простейшей арифметики с десятичными числами, когда мы вполне можем сделать то же самое с помощью чисел с плавающей точкой 🤷‍♂️?

Перед тем, как мы ответим на данный вопрос, мы хотим, чтобы вы сами посчитали в Python, какой результат будет в данном примере: 0.1+0.2? Вы будете удивлены, когда узнаете, что правильный ответ — это не 0,3, а 0,30000000000000004.

Чтобы понять, почему в расчетах возникла ошибка, попробуйте представить 1/3 в десятичной форме. Тогда вы заметите, что число на самом деле не заканчивается в базе 10. Так как все числа должны быть каким-то образом представлены, при их сохранении в консоли делается несколько приближений, что и приводит к ошибкам.

Cпециально для читателей-гуманитариев, у нас есть объяснение принципов работы модулей Питона: «Она на долю секунды отвела взгляд» и "Она отвела взгляд на короткое время" — чувствуете разницу?

Чтобы получить точные результаты, подобные тем, к которым мы привыкли при выполнении расчетов вручную, нам нужно что-то, что поддерживает быструю, точно округленную, десятичную арифметику с плавающей запятой, и модуль Decimal отлично справляется с этой задачей. Теперь, когда мы разобрались с теорией, переходим к принципам работы десятичного модуля.

Модуль 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 "<pyshell#78>", line 1, in <module> print(number.quantize(Decimal('1.00'))) decimal.InvalidOperation: [<class '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, в противном случае к нулю).
>>> from decimal import Decimal, ROUND_CEILING >>> number = Decimal("0.029") >>> print(number.quantize(Decimal("1.00"), ROUND_CEILING)) 0.03

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

Полезные методы 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 считает просто и честно: любители онлайн-игр приспособили его для подсчетов в игровой математике.

1
😭
1
😕
1
😃
29
😍
Комментарии (2)
Зачем я начал учить питон...
4 мес. 1 день назад

Привет Pytonchik! И снова вынужден обратиться за помощью.

Начал писать практическую работу о decimal, мне нужно проверить насколько он медленее float. Я не знаю как это сделать, time выводит ноль секунд если программа отработала меньше 1 секунды, и это понятно. Проблема возникает с модулем timeit. Судя из той теории что я прочёл, модуль timeit выводит минимальное время выполнения, в свою очередь мне нужно среднее значение, хотя в той же теории было сказано что статистика не так важна и лучше ориентироваться на это самое минимальное значение. Ну хорошо, я начал эксперементировать.

import timeit start = timeit.timeit("(0.15 + 0.23 + 0.41)*13") print(start) # 0.15163580002263188

Тут вывело вроде как нормальное значение, насколько я понимаю это 0 секунд и 15 нано секунд. Это минимальное значение при условии что параметр number стоит по умолчанию 1000000. Теперь мы такие "умные" и "о какой же простой модуль" что я подумал пока ознакамливался с ним установить number=100.

import timeit start = timeit.timeit("(0.15 + 0.23 + 0.41)*13", number=100) print(start) # 2.269999822601676e-05

И о чудо! Мы просто сократили количество запуска кода до ста, и вследствии чего код стал обрабатываться 2 секунды, что не соответствует действительности, и по логике не должно так работать, мы же просто сократили количество повторений не меняя сам код.

Смотрим что произойдёт с decimal.

import timeit start = timeit.timeit("(Decimal('0.15') + Decimal('0.23') + Decimal('0.41'))*Decimal('13')", setup="from decimal import Decimal") print(start) # 1.7781018000096083

Ну вот казалось бы из миллиона повторений float в разы быстрее decimal что мы и доказываем, а теперь мы ограничим опять чтобы таймер замерял 100 попыток.

import timeit start = timeit.timeit("(Decimal('0.15') + Decimal('0.23') + Decimal('0.41'))*Decimal('13')", setup="from decimal import Decimal", number=100) print(start) # 0.00021630001720041037

И как мы видим, программа нам говорит что при ста повторений, те 15 нано секунд у float и в подмётки не годятся нашему decimal что так же быть не может.

Очень надеюсь на помощь, т.к. решение этого вопроса в интернете я не нашёл. Подскажи пожалуйста как лучше замерить время, чтобы оно не выдавало таких странных результатов. Укажи что я не правильно может делаю, и что же всё же лучше, ориентироваться на 2 самых быстрых выполнения кода, или всё же всё это суммировать в цикле и делить на количество повторений, т.к. пример из интернета по типу "берем любой прежний код и делим к примеру на 100" нам не выведет среднее значение, должно вывести просто самое быстрое выполнение делённое на 100. Заранее спасибо за помощь.

p.s. number это не повторение timeit, а количество итераций, то есть код из 4 строк при number 7 замерит 4 строки и до третий включительно и это будет общее время, но это не отменяет моего непонимания почему мы миллион раз складываем и умножаем в первом примере за 15 нано секунд, а 100 раз за 2 секунды и почему у нас так или иначе когда миллион раз decimal выполняется, более менее логичное время, а при 100 выполнениях decimal в отличии от float убежал вперед планеты всей, когда это должно быть наоборот...

p.s.s. Заглянув в вашу теорию по числам, я понял что всё выводится правильно, просто в представлении которое я пока не понимаю, 0.00021630001720041037 это число больше чем 2.269999822601676e-05, т.к. e-05 означает количество нулей. Правда я не понимаю что означает 05, то есть было бы -5 было бы еще 5 нулей, 1 в основной части и 4 в дробной, но 05 не понимаю, как воспринимать. Чтобы мне это понять мне нужно было убить 6 часов на элементарные базовые вещи, и написать длиннющий коментарий, который теперь всё что выше не имеет смысла... Подскажите где можно подробнее почитать с числами j и e, и что эти буквы впринципе значат, т.к. из math это некое похожее число 2,78, тогда зачем оно в этой записи вывода, что оно означает?

читать дальше
0
ответить
Pythonchik
4 мес. назад

Вы все правильно написали в p.s.s.

2.269999822601676e-05 — это экспоненциальная запись числа. Она используется при очень больших или очень малых числах. В данном случае перед нами очень маленькое число 0.0000227.

Перевести в Python из экспоненциальной записи в обычную можно так:

print('{:0.7f}'.format(2.269999822601676e-05)) # 0.0000227

Но при тестировании лучше задать побольше итераций, чтобы результаты были в секундах.

2
ответить
Может понравиться
Округление в Python
Математика
upd:
Округление в Python — round, int, модуль math