Decimal числа. Отличия от float
После рассказа про float меня просили рассказать про Decimal. Узнаем же, что это за зверь, как он устроен внутри и как с ним работать. Итак, Decimal – это класс из стандартного модуля decimal. Он представляет собой число с плавающей точкой, как и float. Да, именно с плавающей, потому что некоторые, я слышал, думают, что это число с фиксированной точкой.
Однако, Decimal имеет ряд существенных отличий от float.
Тип Decimal создан, чтобы операции над рациональными числами в компьютере выполнялись также, как они выполняются людьми, как их преподают в школе. Иными словами, чтобы все-таки 0.1 + 0.1 + 0.1 == 0.3 . Из-за ошибок представления, float приводит к утере точности, и такие простые на первый взгляд равенства не выполняются. А это может быть критично в высокоточных научных вычислениях, и главное в сфере бизнеса и финансов!
Внутреннее устройство
float – реализован по стандарту IEEE-754 как число с плавающей запятой двойной точности (64 бита) на основании 2 . Реализация таких чисел заложена прямо в железо почти любого современного процессора. Поэтому float в Python работает примерно также, как и double в С, С++, Java и прочих языках. И имеет такие же ограничения и «странности». Так как поддержка float имеет аппаратный характер, то его быстродействие сравнительно велико.
Decimal – число с плавающей точкой с основанием экспоненты – 10 , отсюда и название (decima лат. – десятая часть, десятина).
Он реализован по стандарту IBM: General Decimal Arithmetic Specification Version 1.70 – 7 Apr 2009, который в свою очередь основан на стандартах IEEE. По поводу реализации, в исходниках CPython я нашел два варианта: на чистом Python и с помощью Си-библиотеки libmpdec. Обе реализации есть в кодовой базе, хотя в последних версиях Python 3 используется именно Си-версия, очевидно, она в разы быстрее! Видите букву Си?
Поэтому первый важный вывод:
Хоть Decimal и написан на Си, он в разы медленнее, чем float, так как реализован программно, а float – аппаратно.
Когда, речь идет о деньгах, считать много обычно не требуется, зато требуется точность, иначе просто ваш баланс не сойдется. Представьте, что в банке вы не можете снять свой миллион, потому что из-за ошибки у вас не хватает одной триллионной копейки? Абсурдно же.
Теперь самое главное – основание 10. Оно позволяет записывать десятичные дроби точно, без ошибок представления.
Мантисса и экспоненты – целые числа.
Помните, что мы не могли представить 0.1 в float с основанием 2? С основанием 10 – это элементарно:
0.1 = 1 * 10 -1 , и таким образом, 0.3 = 3 * 10 -1 = (1 + 1 + 1) * 10 -1 = 0.1 + 0.1 + 0.1
Как мы в школе учили десятичные дроби и знаем, как оперировать ими, так и здесь. Все точно и привычно. Ну почти все. Если мы разделим единицу на тройку, то получим бесконечную периодическую дробь 0.33333333…, либо по другому ее пишут 0.(3) – три в периоде. Естественно, что бесконечных чисел, записанных цифрами в памяти компьютера быть не может, иначе бы потребовалась бесконечная память. Поэтому количество троек в записи может быть большим, но обязано быть конечным.
Decimal оперирует с числами с произвольной (задаваемой пользователем), но конечной точностью.
По умолчанию точность – 28 десятичных знаков.
Еще одно следствие того, что Decimal реализовано программно – то, что его можно на ходу настраивать, как угодно пользователю. Для этого есть контекст – объект, содержащий настройки для выполнения операций и флаги. Операции выполняемые в этом контексте следуют правилам, заданным в нем. В отличии от float, где все правила фиксированы на аппаратном или низшим программным уровнях. Настроить можно:
- Точность выполнения операций в количестве десятичных знаках
- Режимы округления (их целых 8 штук)
- Пределы по экспоненте
- Режимы обработки исключительных ситуаций – настройки сигналов (например, деление на ноль, переполнение и прочее).
Флаги в контексте устанавливаются со стороны модуля decimal, если при последнем вычислении случился какой-то из сигналов. (Это отдельная тема, о ней потом.)
Сам же объект Decimal содержит знак, мантиссу (коэффициент перед экспонентой) и саму экспоненту (степень). Лишние нули в мантиссе на обрезаются, чтобы сохранять значимость числа ( 1.20 * 2.40 = 2.8800 ).
Decimal – иммутабельный (неизменяемый) тип. Операции над ним приводят к созданию новых объектов, а старые не меняются.
Поработаем с Decimal
Начинаем с импорта и посмотрим, каков контекст по умолчанию:
Мы видим здесь, что точность 28 знаков, округление к ближайшему четному, пределы по экспоненте ±999999, capitals – это про заглавную Е при печати, clamp не будем трогать пока что, флаги все сброшены, а включенные ловушки – неправильная операция, деление на ноль, переполнение. Если ловушка включена, это значит, что при возникновении соответствующего сигнала будет брошено исключение. Если нет ловушки, то при сигнале будет только втихую установлен флаг. Я оставлю тему ловушек на следующую статью.
Создание Decimal
Создать Decimal можно из обычного целого числа, из float, из строки или кортежа. С обычным числом все просто – int представлены и так точно:
Из float – надо быть очень аккуратным. Потому что, float округляется внутри до ближайшего возможного, а Decimal не знает о ваших первоначальных намерениях, поэтому копирует содержимое float. К примеру, числа 0.1 в представлении float просто не существует. Python считывает 0.1 из кода как строку, потому ищет наиболее близкий к нему возможный float, а из него уже копируется содержимое в Decimal, как есть – уже с ошибкой:
Не рекомендуется создавать Decimal из float. В Decimal попадет уже неправильно округленное число. Создавайте Decimal из целых чисел, либо из строк!
Логически правильно создавать Decimal сразу из строки, избегая фазу с превращением его в float! Что есть в строке – попадет в Decimal. Может показаться, что это немного криво – хранить числах в строках, но теперь вы знаете о представлении двоичного float, и строки обретают реальный смысл.
Можно строкой еще задавать бесконечности и NaN (не число). Примеры:
Если использовать кортеж для конструирования Decimal, то он должен содержать три элемента:
- Знак, как число: 0 – это плюс, 1 – это минус.
- Кортеж из значащих цифр мантиссы
- Число – показатель экспоненты
Вообще кортеж для Decimal использует редко. Но вот вам пример:
Если число слишком большое, то будет сигнал – неправильная операция. А так как на этом сигнале ловушка по умолчание – то будет исключение:
Точность представление Decimal задается исключительно длиной задающего числа (или длиной строки). Настройки точности и режимов округления из контекста в ступают в игру только во время совершения математических операций.
Decimal отлично интегрирован в среду Python. Что касается математики, то с Decimal работают все привычные операции: сложение, вычитание, умножение, деление, возведение в степень и так далее.
Работайте с Decimal как с обычными числами: складывайте, вычитайте, умножайте, делите и прочее. Можете, миксовать их с целыми числами. Но не рекомендуется миксовать их с float.
Дополнительно еще доступны некоторые математические функции:
А вот чисел π и e из коробки не завезли, потому что не ясно, какая точность вам нужна. Их можно взять из модуля math в виде float или задать вручную до нужной точности или на худой конец вычислить. Аналогично для тригонометрии и специальных функций: либо берите неточные значения из math, либо вычисляйте сами до нужной точности рядами Тейлора или другими методами с помощью примитивных операций. В документации есть примеры вычисления констант и функций.
Кстати, Decimal можно передавать как аргументы функций, ожидающих float. Тогда они будут преобразованы во float:
Метод quantize округляет число до фиксированной экспоненты, полезно для финансовых операций, когда нужно округлить копейки (центы). Первый аргумент – Decimal – что-то вроде шаблона округления. Смотрите примеры:
Кроме того Decimal можно сравнивать между собой, как обычные числа. Причем допускается сравнивать даже на точное равенство:
Можно сортировать списки Decimal, искать минимум и максимум. А также преобразовывать Decimal обратно в обычные типы int, float, str. Пример из документации:
Но! Не все сторонние библиотеки поддерживают Decimal. Например, не получится использовать его для numpy!
Не все операции над Decimal абсолютно точные, если результат неточен, то возникает сигнал Inexact.
Выводы
Выбор между Decimal и float – это поиск компромисса с учетом условий конкретной задачи.
Если вам нужно считать очень много (симуляции, физика, химия, графика, игры), то иногда имеет смысл отказаться от точности Decimal в пользу скорости и компактности хранения данных у float. В бизнесе и финансах считать приходится не очень много, но нужно делать это предельно точно, тогда ваш взгляд должен обратиться в сторону Decimal. В таблице вы найдете сравнение этих двух типов данных.
Примечание: а для целочисленных вычислений может сгодится и простой int, он умеет из коробки длинную математику!
В этой статье я не осветил полностью вопросы:
- Сигналы, флаги и ловушки
- Обзор режимов округления
- Управление контекстами
- Контексты и многопоточность
Если сообществу будет интересно, то я продолжу тему. Голосование будет на канале!
Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈
Источник
BestProg
Числа с фиксированной точностью. Класс Decimal
Содержание
Поиск на других ресурсах:
1. Особенности использования чисел с фиксированной точностью. Класс Decimal
Числа с фиксированной точностью – это числа типа Decimal , которые при вычислениях используют фиксированное количество знаков после запятой. Тип Decimal – это специально разработанный класс (начиная с версии Python 2.4).
Понятие «фиксированная точность» означает, что с помощью таких чисел можно сохранять значение которое будет всегда иметь определенное количество знаков после запятой.
Например, нужно сохранять числа строго с числом 6 знаков после запятой.
Класс Decimal реализован в модуле decimal . Чтобы использовать возможности класса Decimal нужно выполнить команду
2. Как с помощью класса Decimal задать нужную фиксированную точность? Примеры
Чтобы создать объект класса Decimal используется конструктор этого класса. Конструктор получает строку с числом, в котором указывается заданная точность, например
3. Пример необходимости применения класса Decimal в программах на Python
В примере продемонстрирована необходимость написания программ с использованием класса Decimal для случаев, когда точность вычисления крайне важна.
Результат работы программы
Сначала создается объект (переменная) с именем a , в которую записывается сумма
Затем значение этой переменной выводится на экран. Как видно из результата, результат вычисления переменной a содержит погрешность. Это связано с тем, что память, которая выделяется для чисел вещественного типа, ограничена. Другими словами, количество бит в представлении вещественных чисел есть недостаточным.
На следующем шаге создается объект с именем b , в который записывается сумма с использованием класса Decimal
После вывода значения b на экран, видно что значение переменной b представлено точно без погрешности.
4. Пример использования класса Decimal и функции str()
В предыдущем примере конструктор класса Decimal получал строку с числом
в котором определялась точность (1 знак после запятой) и значение числа 0.2.
Возможна ситуация, когда нужно передать непосредственно число а не строку. В этом случае удобно использовать функцию str() , как показано ниже
Функция str() получает число и переводит его в строку
5. Использование фиксированной точности для чисел с разной точностью представлений. Пример
Возможна ситуация, когда в выражении, содержащем класс Decimal , имеются числа с разной точностью представлений. В этом случае точность результата автоматически устанавливается равной точности числа с наибольшей точностью представления.
Например. При сложении трех чисел
автоматически устанавливается точность 3 знака после запятой, поскольку конструктор
определяет число 0.001 с наибольшей точностью представления.
6. Создание объектов класса Decimal из вещественных чисел. Пример
Для случаев, когда есть в наличии вещественное число, можно создать объект класса Decimal . В этом случае используется метод from_float() класса Decimal .
Результат выполнения вышеприведенного кода
Как видно из результата, не всегда удается получить фиксированную точность при использовании метода from_float() .
7. Глобальная настройка точности. Пример
Бывают случаи, когда точность в программе нужно задать для всех операций текущего потока управления. Это может быть, например, представление денежных сумм с учетом копеек (2 знака после запятой).
Пример.
Результат выполнения программы
В вышеприведенном примере глобальная точность для класса Decimal задается с помощью функции getcontext() , которая возвращает объект контекста в этом модуле. Точность задается в текущем потоке управления.
Источник
Использование модуля decimal в Python
В данном руководстве мы рассмотрим использование модуля для работы с десятичными числами, который включен в стандартную библиотеку Python.
Введение
В Python есть числовые типы данных, такие как int, float и комплексные числа, но из-за машинной зависимости чисел с плавающей точкой нам нужен более точный тип данных для вычислений, требующих высокой точности. В этой статье мы рассмотрим модуль decimal в Python, который реализует десятичные числа с точностью до 28 цифр.
Использование
Python реализует десятичные числа как числа двойной точности с плавающей точкой, которые зависят от машины. Для вычислений, где точность критична для бизнеса, числа с плавающей точкой могут вызывать ошибки при выполнении на другой машине. Поэтому для таких приложений нам нужен независимый от машины тип данных для реализации десятичных чисел, который был реализован с помощью модуля decimal в Python. Кроме того, модуль decimal реализует десятичное число с точностью до 28 десятичных цифр, в то время как числа с плавающей запятой имеют точность до 18 цифр.
Это можно наблюдать в следующем примере
Вторая причина использования десятичных чисел вместо чисел с плавающей точкой заключается в том, что в Python числа не могут быть представлены с использованием их точного значения, а используется только приближение, что может быть опасно для критически важных программ.
Из-за аппроксимации значения с плавающей точкой дают разные результаты для разных вычислений. Например, если мы сложим 1,2 и 2,2, используя значения с плавающей точкой, ответ должен быть равен 3,4. Но когда мы сравним добавленное число и 3,4, они не будут равны. Эта ошибка возникает из-за приближений в числах с плавающей запятой, из-за которых сумма 1,2 и 2,2 не равна 3,4.
Поэтому в случаях, когда необходимо сравнить десятичные значения, следует использовать десятичный модуль, а не числа с плавающей точкой.
Это может быть более понятно из следующего примера.
Настройка контекста
Чтобы использовать модуль decimal в Python, достаточно его импортировать следующим образом.
Импортированный модуль имеет предопределенный контекст, который содержит значения по умолчанию для точности, округления, флагов, минимально и максимально допустимых чисел. Мы можем посмотреть значения контекста с помощью метода getcontext() следующим образом.
Мы также можем установить точность и другие параметры контекста. Чтобы изменить точность на 2 цифры вместо 28, мы можем использовать следующую программу.
По умолчанию, при округлении десятичных чисел с помощью модуля decimal, числа округляются равномерно. Мы можем изменить это поведение, изменив значение «rounding» в контексте следующим образом.
Все арифметические операции над десятичными числами, определяемые модулем decimal, аналогичны операциям над числами с плавающей запятой. Разница заключается в точности значений из-за различий в реализации.
Округление значений
Мы можем округлять числа до определенных цифр с помощью функции round(). Функция round принимает десятичное число, которое нужно округлить, в качестве первого аргумента и количество цифр, до которых его нужно округлить, в качестве второго аргумента и возвращает округленное десятичное значение следующим образом.
Сравнение десятичных чисел
Как было показано в одном из разделов выше, сравнение чисел с плавающей запятой может привести к неправильным результатам, но десятичные числа являются точными, и их сравнение всегда даст желаемый результат. Это можно увидеть следующим образом.
В приведенном выше коде видно, что мы преобразовали строки в десятичные числа вместо того, чтобы преобразовывать числа с плавающей точкой в десятичные. Это сделано потому, что если мы преобразуем число с плавающей точкой в десятичное, то ошибка аппроксимации распространится на десятичное число, и мы не получим желаемого результата.
Заключение
В этой статье мы изучили недостатки выполнения арифметических операций с числами с плавающей точкой и использовали модуль decimal для реализации тех же операций без ошибок в Python. Мы также можем написать программы, использованные в этой статье, с обработкой исключений, используя python try except, чтобы сделать программы более надежными и систематически обрабатывать ошибки.
Оставайтесь с нами для получения новых информативных статей.
Источник