На этом шаге мы рассмотрим использование модуля decimal.
Вам нужно выполнить точные вычисления с десятичными дробями, и вы хотите избавиться от небольших ошибок, которые обычно возникают при работе с числами с плавающей точкой.
Широко известный недостаток чисел с плавающей точкой в том, что они не могут точно представить все 10 базовых десятичных цифр. Более того, даже простые математические вычисления приводят к появлению небольших ошибок. Например:
>>> a = 4.2 >>> b = 2.1 >>> a + b 6.300000000000001 >>> (a + b) == 6.3 False >>>
Эти ошибки - "особенность" процессора и стандарта представления чисел с плавающей точкой IEEE 754, на основе которого работает модуль процессора для вычислений с плавающей точкой. Поскольку в типе данных "числа с плавающей точкой" Python хранит данные, используя нативное представление, вы ничего не можете сделать, чтобы избавиться от ошибок при использовании экземпляров float.
Если вам нужна большая точность (и вы готовы в некоторой степени поступиться производительностью), то можете использовать модуль decimal:
>>> from decimal import Decimal >>> a = Decimal('4.2') >>> b = Decimal('2.1') >>> a + b Decimal('6.3') >>> print(a + b) 6.3 >>> (a + b) == Decimal('6.3') True >>>
На первый взгляд он может показаться странным (например, определение чисел как строк). Однако объекты Decimal работают именно так, как вы можете ожидать (поддерживают все обычные математические операции и т. д.). Если вы выводите их или используете в функциях форматирования строк, они выглядят как обычные числа.
Главное преимущество decimal в том, что он позволяет контролировать различные аспекты вычислений, такие как число знаков после точки и округление. Чтобы это сделать, вы создаете локальный контекст и меняете его установки. Например:
>>> from decimal import localcontext >>> a = Decimal('1.3') >>> b = Decimal('1.7') >>> print(a / b) 0.7647058823529411764705882353 >>> with localcontext() as ctx: ctx.prec = 3 print(a / b) 0.765 >>> with localcontext() as ctx: ctx.prec = 50 print(a / b) 0.76470588235294117647058823529411764705882352941176 >>>
Модуль decimal реализует "Общую спецификацию десятичной арифметики" ("General Decimal Arithmetic Specification") компании IBM. Нет нужды упоминать, что у него есть очень много различных опций для конфигурирования, описание которых лежит за пределами возможностей данного изложения.
Новички в Python могут склоняться к повсеместному использованию модуля decimal для решения проблемы неточности, которая неизбежна при работе с типом данных float. Однако важно понимать область применения вашего приложения. Если вы работаете с научными или инженерными данными, компьютерной графикой, то вполне нормально использовать обычный тип данных чисел с плавающей точкой. В общем-то, очень немногие вещи в реальном мире измеряются с точностью до 17-го знака после точки, которую предоставляет float. Так что небольшие ошибки не так уж важны. А производительность нативных чисел с плавающей точкой заметно выше, что важно при выполнении большого количества вычислений.
Но вы не должны просто полностью игнорировать ошибки. Математики проводят немало времени, изучая различные алгоритмы, и некоторые обрабатывают ошибки лучше других. Вы также должны быть осторожными с эффектами таких штук, как вычитательная потеря точности и сложение больших и маленьких чисел. Например:
>>> nums = [1.23e+18, 1, -1.23e+18] >>> sum(nums) # Заметьте, как исчезает 1 0.0 >>>
Ошибка из последнего примера может быть решена путем использования math.fsum():
>>> import math >>> math.fsum(nums) 1.0 >>>
Однако для других алгоритмов вам придется изучить реализацию и понять, как они работают с точки зрения подобных ошибок.
Подведем итог: модуль decimal используется в основном в финансовых и прочих подобных приложениях. В таких программах небольшие ошибки в вычислениях ужасно мешают, а decimal позволяет от них избавиться. Также часто можно встретить объекты класса Decimal в интерфейсах Python к базам данных - опять же, особенно часто их используют для доступа к финансовым данным.
На следующем шаге мы рассмотрим форматирование чисел для вывода.