Как избежать арифметических ошибок с вещественными числами в Python
Мы рассчитываем на точность при написании кода. В конце концов, работу выполняет компьютер. Тем не менее, ваши вычисления могут быть не совсем верными, а вы даже не будете подозревать об этом.
Если вы сталкивались с арифметическими ошибками при работе с числами с плавающей точкой, то вы знаете, о чём идёт речь. Рассмотрим конкретный пример:
print(1.1 * 3) # 3.3000000000000003
Так происходит из-за того, что десятичные числа на самом деле хранятся в виде формулы и не имеют точного представления.
Давайте попробуем найти решение данной проблеме с помощью встроенной библиотеки Decimal.
Как пользоваться библиотекой Decimal
Согласно официальной документации Python:
Модуль decimal обеспечивает быстрое и корректное округление десятичных чисел с плавающей точкой.
Как нам воспользоваться этим инструментом? Начнём с импорта библиотеки. Чтобы импортировать все её компоненты будем использовать символ *
.
from decimal import *
Далее мы используем конструктор Decimal()
со строковым значением внутри для создания нового объекта и снова попробуем выполнить наши арифметические действия.
print(Decimal('1.1') * 3) # 3.3
Убедитесь, что работаете именно со строковым значением. В противном случае число с плавающей точкой 1.1
будет преобразовано в Decimal-объект, и ошибка сохранится, а возможно и усугубится.
print(Decimal(1.1) * 3) # 3.300000000000000266453525910
«Золотое правило»
В принципе, вы можете использовать Decimal-объекты таким же образом, как и любые другие числовые значения. Однако, если вы решите использовать библиотеку Decimal, помните об одном «золотом правиле»: не смешивайте Decimal-объекты с float-объектами.
Если вы будете использовать Decimal-числа и float-числа вместе, то, скорее всего, получите ошибку. В случае арифметических вычислений эти два типа данных несовместимы.
a = Decimal('1.1') b = 2.2 c = a + b # TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
Ниже приведены дополнительные советы и рекомендации по использованию Decimal()
.
Используйте .quantize()
для округления
Передайте в метод .quantize()
Decimal-объект с имеющимся количеством знаков после запятой. Это бывает особенно полезно при работе с денежными единицами. В примере ниже мы округлим значение до двух знаков после запятой.
a = Decimal('1.123456789') b = a.quantize(Decimal('1.00')) print(b) # 1.12
Для настройки точности округления используйте getcontext()
Количество цифр после запятой можно настроить, изменив текущий контекст.
Контекст вычислений определяет точность, правила округления, ограничения на экспоненты, флаги результатов операций и средства вызова исключений.
Сначала давайте взглянем на то, как выглядит текущий контекст, а затем продемонстрируем, что происходит, когда мы вносим в него изменения.
from decimal import * print(getcontext()) """ Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow]) """
print(Decimal(1) / Decimal(3)) # 0.3333333333333333333333333333
getcontext().prec = 4
print(Decimal(1)/Decimal(3)) # 0.3333
Будьте осторожны с операциями взятия остатка от деления при отрицательных числах
Операция %
возвращает остаток от операции деления. Обычно при использовании отрицательного числа сохраняется знак делителя.
print((-7) % 4) # 1 print(7 % (-4)) # -1
В случае же применения Decimal-объекта сохраняется знак делимого.
print(Decimal(-7) % 4) # -3 print(7 % Decimal(-4)) # 3
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻
Источник: medium.com
Перевод и адаптация: Ирина Гарибян