Decimal python что это
При работе с числами с плавающей точкой (то есть float) мы сталкиваемся с тем, что в результате вычислений мы получаем не совсем верный результат:
number = 0.1 + 0.1 + 0.1 print(number) # 0.30000000000000004
Проблему может решить использование функции round() , которая округлит число. Однако есть и другой способ, который заключается в использовании встроенного модуля decimal .
Ключевым компонентом для работы с числами в этом модуле является класс Decimal . Для его применения нам надо создать его объект с помощью конструктора. В конструктор передается строковое значение, которое представляет число:
from decimal import Decimal number = Decimal("0.1")
После этого объект Decimal можно использовать в арифметических операциях:
from decimal import Decimal number = Decimal("0.1") number = number + number + number print(number) # 0.3
В операциях с Decimal можно использовать целые числа:
number = Decimal("0.1") number = number + 2
Однако нельзя смешивать в операциях дробные числа float и Decimal:
number = Decimal("0.1") number = number + 0.1 # здесь возникнет ошибка
С помощью дополнительных знаков мы можем определить, сколько будет символов в дробной части числа:
number = Decimal("0.10") number = 3 * number print(number) # 0.30
Строка «0.10» определяет два знака в дробной части, даже если последние символы будут представлять ноль. Соответственно «0.100» представляет три знака в дробной части.
Округление чисел
Объекты Decimal имеют метод quantize() , который позволяет округлять числа. В этот метод в качестве первого аргумента передается также объект Decimal, который указывает формат округления числа:
from decimal import Decimal number = Decimal("0.444") number = number.quantize(Decimal("1.00")) print(number) # 0.44 number = Decimal("0.555678") print(number.quantize(Decimal("1.00"))) # 0.56 number = Decimal("0.999") print(number.quantize(Decimal("1.00"))) # 1.00
Используемая строка «1.00» указывает, что округление будет идти до двух знаков в дробной части.
По умолчанию округление описывается константой ROUND_HALF_EVEN , при котором округление происходит до ближайшего четного числа, если округляемая часть равна 5. Например:
from decimal import Decimal, ROUND_HALF_EVEN number = Decimal("10.025") # 2 - ближайшее четное число print(number.quantize(Decimal("1.00"), ROUND_HALF_EVEN)) # 10.02 number = Decimal("10.035") # 4 - ближайшее четное число print(number.quantize(Decimal("1.00"), ROUND_HALF_EVEN)) # 10.04
Стратегия округления передается в качестве второго параметра в quantize.
Строка «1.00» означает, что округление будет идти до двух чисел в дробной части. Но в первом случае «10.025» — вторым знаком идет 2 — четное число, поэтому, несмотря на то, что следующее число 5, двойка не округляется до тройки.
Во втором случае «10.035» — вторым знаком идет 3 — нечетное число, ближайшим четным числом будет 4, поэтому 35 округляется до 40.
Данное поведение при округлении, возможно, не всем покажется желательным, и в этом случае его можно переопределить, использовав одну из следующих констант:
- ROUND_HALF_UP : округляет число в сторону повышения, если после него идет число 5 или выше
- ROUND_HALF_DOWN : округляет число в сторону повышения, если после него идет число больше 5
number = Decimal("10.026") print(number.quantize(Decimal("1.00"), ROUND_HALF_DOWN)) # 10.03 number = Decimal("10.025") print(number.quantize(Decimal("1.00"), ROUND_HALF_DOWN)) # 10.02
number = Decimal("10.005") print(number.quantize(Decimal("1.00"), ROUND_05UP)) # 10.01 number = Decimal("10.025") print(number.quantize(Decimal("1.00"), ROUND_05UP)) # 10.02
number = Decimal("10.021") print(number.quantize(Decimal("1.00"), ROUND_CEILING)) # 10.03 number = Decimal("10.025") print(number.quantize(Decimal("1.00"), ROUND_CEILING)) # 10.03
number = Decimal("10.021") print(number.quantize(Decimal("1.00"), ROUND_FLOOR)) # 10.02 number = Decimal("10.025") print(number.quantize(Decimal("1.00"), ROUND_FLOOR)) # 10.02
Модуль decimal в Python, десятичная арифметика
Модуль decimal обеспечивает поддержку быстрой правильно округленной десятичной арифметики с плавающей запятой. Он предлагает несколько преимуществ по сравнению с типом данных float :
- Десятичное число основано на модели с плавающей точкой, которая была разработана с учетом потребностей людей и обязательно имеет первостепенный руководящий принцип — компьютеры должны обеспечивать арифметику, которая работает так же, как арифметика, которую люди изучают в школе.
- Десятичные числа могут быть представлены точно. Напротив, числа типа float 1.1 и 2.2 не имеют точных представлений в двоичной формате. Конечные пользователи обычно не ожидают, что 1.1 + 2.2 будет отображаться как 3.3000000000000003.
- Точность переносится в арифметику. В десятичном формате с плавающей запятой 0,1 + 0,1 + 0,1 — 0,3 точно равно нулю. В двоичной формате результат равен 5.5511151231257827e-017. Хотя значения близки к нулю, различия мешают надежному тестированию на равенство и различия могут накапливаться. По этой причине десятичная дробь предпочтительнее в бухгалтерских приложениях, которые имеют строгие инварианты равенства.
- Десятичный модуль включает в себя понятие значимых мест, так что 1,30 + 1,20 составляет 2,50. Конечный ноль сохраняется для обозначения значимости. Это обычное представление для денежных приложений. Например 1,3 * 1,2 дает 1,56, а 1,30 * 1,20 дает 1,5600.
- В отличие от аппаратной двоичной плавающей запятой, модуль decimal имеет изменяемую пользователем точность, по умолчанию до 28 разрядов, которая может быть настолько большой, насколько это необходимо для вычислений:
from decimal import * getcontext().prec = 6 Decimal(1) / Decimal(7) # Decimal('0.142857') getcontext().prec = 28 Decimal(1) / Decimal(7) # Decimal('0.1428571428571428571428571429')
Дизайн модуля основан на трех понятиях: десятичное число, контекст для арифметики и сигналы.
Десятичное число является неизменным. У него есть знак, цифры коэффициента и показатель степени. Для сохранения значимости цифры коэффициента не усекают конечные нули. Десятичные числа также включают специальные значения, такие как Infinity , -Infinity и NaN . Стандарт также отличает -0 от +0.
Контекст для арифметики — это среда, определяющая точность, правила округления, ограничения на экспоненты, флаги, указывающие результаты операций, и средства активации, которые определяют, будут ли сигналы рассматриваться как исключения.
Сигналы — это группы исключительных условий, возникающих в процессе вычислений. В зависимости от потребностей приложения сигналы могут игнорироваться, рассматриваться как информационные или рассматриваться как исключения. Смотрите раздел «Сигналы в модуле decimal «.
Для каждого сигнала есть флаг и активатор ловушек. Когда сигнал встречается, его флаг устанавливается в единицу, затем, если активатор прерывания установлен в единицу, возникает исключение. Флаги являются липкими, поэтому пользователь должен сбросить их перед мониторингом расчетов.
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Краткое руководство по модулю decimal
- Ошибки округления с повышенной точностью
- Способы работы с классом Decimal
- Класс Decimal() модуля decimal
- Методы объекта Decimal()
- Контексты модуля decimal
- Класс Context() модуля decimal
- Режимы округления модуля decimal
- Сигнальные флаги модуля decimal
Числа с фиксированной точностью. Класс Decimal
Числа с фиксированной точностью – это числа типа Decimal , которые при вычислениях используют фиксированное количество знаков после запятой. Тип Decimal – это специально разработанный класс (начиная с версии Python 2.4).
Понятие «фиксированная точность» означает, что с помощью таких чисел можно сохранять значение которое будет всегда иметь определенное количество знаков после запятой.
Например, нужно сохранять числа строго с числом 6 знаков после запятой.
Класс Decimal реализован в модуле decimal . Чтобы использовать возможности класса Decimal нужно выполнить команду
from decimal import Decimal
2. Как с помощью класса Decimal задать нужную фиксированную точность? Примеры
Чтобы создать объект класса Decimal используется конструктор этого класса. Конструктор получает строку с числом, в котором указывается заданная точность, например
Decimal('0.002') # фиксированная точность 3 знака после запятой Decimal('0.23') # фиксированная точность 2 знака после запятой Decimal('0.00001') # фиксированная точность 5 знаков после запятой
3. Пример необходимости применения класса Decimal в программах на Python
В примере продемонстрирована необходимость написания программ с использованием класса Decimal для случаев, когда точность вычисления крайне важна.
# Числа с фиксированной точностью. Класс Decimal - преимущества применения # подключить класс Decimal из модуля decimal from decimal import Decimal # обычное вычисление, существует погрешность a = 0.2+0.2+0.2-0.4 # a = 0.20000000000000007 - погрешность (. ) print('a = ', a) # вычисление с помощью класса Decimal b = Decimal('0.2')+Decimal('0.2')+Decimal('0.2')-Decimal('0.4') print('b = ', b) # b = 0.2 - точный результат
Результат работы программы
a = 0.20000000000000007 b = 0.2
Сначала создается объект (переменная) с именем a , в которую записывается сумма
0.2+0.2+0.2–0.4
Затем значение этой переменной выводится на экран. Как видно из результата, результат вычисления переменной a содержит погрешность. Это связано с тем, что память, которая выделяется для чисел вещественного типа, ограничена. Другими словами, количество бит в представлении вещественных чисел есть недостаточным.
На следующем шаге создается объект с именем b , в который записывается сумма с использованием класса Decimal
b = Decimal('0.2')+Decimal('0.2')+Decimal('0.2')-Decimal('0.4')
После вывода значения b на экран, видно что значение переменной b представлено точно без погрешности.
4. Пример использования класса Decimal и функции str()
В предыдущем примере конструктор класса Decimal получал строку с числом
Decimal('0.2')
в котором определялась точность (1 знак после запятой) и значение числа 0.2.
Возможна ситуация, когда нужно передать непосредственно число а не строку. В этом случае удобно использовать функцию str() , как показано ниже
# вычисление с помощью класса Decimal b = Decimal(str(0.2))+Decimal(str(0.2))+Decimal(str(0.2))-Decimal(str(0.4)) print('b = ', b) # b = 0.2
Функция str() получает число и переводит его в строку
x = str(0.2) # x = '0.2'
5. Использование фиксированной точности для чисел с разной точностью представлений. Пример
Возможна ситуация, когда в выражении, содержащем класс Decimal , имеются числа с разной точностью представлений. В этом случае точность результата автоматически устанавливается равной точности числа с наибольшей точностью представления.
Например. При сложении трех чисел
c = Decimal('0.1')+Decimal('0.001')+Decimal(str(0.01)) # c = 0.111
автоматически устанавливается точность 3 знака после запятой, поскольку конструктор
Decimal('0.001')
определяет число 0.001 с наибольшей точностью представления.
6. Создание объектов класса Decimal из вещественных чисел. Пример
Для случаев, когда есть в наличии вещественное число, можно создать объект класса Decimal . В этом случае используется метод from_float() класса Decimal .
# Создание объекта типа Decimal, не всегда работает # Случай 1. Искаженная точность # x1 = 2.479999999999999982236431605997495353221893310546875 x1 = Decimal.from_float(2.48) print('x1 =',x1) # Случай 2. Фиксированная точность x2 = Decimal.from_float(2.5) # x2 = 2.5 - правильная точность print('x2 =', x2)
Результат выполнения вышеприведенного кода
x1 = 2.479999999999999982236431605997495353221893310546875 x2 = 2.5
Как видно из результата, не всегда удается получить фиксированную точность при использовании метода from_float() .
7. Глобальная настройка точности. Пример
Бывают случаи, когда точность в программе нужно задать для всех операций текущего потока управления. Это может быть, например, представление денежных сумм с учетом копеек (2 знака после запятой).
Пример.
# Глобальное задание точности # подключить класс Decimal from decimal import Decimal a = Decimal(5)/Decimal(13) # точность не задана print('a = ', a) # a = 0.3846153846153846153846153846 # задание точности 6 знаков после запятой import decimal decimal.getcontext().prec=6 b = Decimal(5)/Decimal(13) print('b = ', b) # b = 0.384615 c = Decimal(6)/Decimal(13) print('c = ', c) # c = 0.461538
Результат выполнения программы
a = 0.3846153846153846153846153846 b = 0.384615 c = 0.461538
В вышеприведенном примере глобальная точность для класса Decimal задается с помощью функции getcontext() , которая возвращает объект контекста в этом модуле. Точность задается в текущем потоке управления.
Связанные темы
- Представление чисел разных форматов. Базовые числовые типы. Функции преобразования чисел
- Рациональные числа. Класс Fraction
Модули 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 вы можете создавать десятичные числа.
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 считает просто и честно: любители онлайн-игр приспособили его для подсчетов в игровой математике.
Комментарии (2)
Опубликовать
Зачем я начал учить питон.
20 дней назад
Привет 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, тогда зачем оно в этой записи вывода, что оно означает?