Python | Модуль decimal

Последнее обновление: 04.02.2022

При работе с числами с плавающей точкой (то есть 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
    
  • ROUND_05UP: округляет 0 до единицы, если после него идет число 5 и выше

    
    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
  • ROUND_CEILING: округляет число в большую сторону вне зависимости от того, какое число идет после него

    
    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
    
  • ROUND_FLOOR: не округляет число вне зависимости от того, какое число идет после него

    
    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
    

НазадСодержаниеВперед

Модуль math | Python 3 для начинающих и чайников

Модуль math – один из наиважнейших в Python. Этот модуль предоставляет обширный функционал для работы с числами.

math.ceil(X) – округление до ближайшего большего числа.

math.copysign(X, Y) — возвращает число, имеющее модуль такой же, как и у числа X, а знак — как у числа Y.

math.fabs(X) — модуль X.

math.factorial(X) — факториал числа X.

math.floor(X) — округление вниз.

math.fmod(X, Y) — остаток от деления X на Y.

math.frexp(X) — возвращает мантиссу и экспоненту числа.

math.ldexp(X, I) — X * 2i. Функция, обратная функции math.frexp().

math.fsum(последовательность) — сумма всех членов последовательности. Эквивалент встроенной функции sum(), но math.fsum() более точна для чисел с плавающей точкой.

math.isfinite(X) — является ли X числом.

math.isinf(X) — является ли X бесконечностью.

math.isnan(X) — является ли X NaN (Not a Number — не число).

math.modf(X) — возвращает дробную и целую часть числа X. Оба числа имеют тот же знак, что и X.

math.trunc(X) — усекает значение X до целого.

math.exp(X) — eX.

math.expm1(X) — eX — 1. При X → 0 точнее, чем math.exp(X)-1.

math.log(X, [base]) — логарифм X по основанию base. Если base не указан, вычисляется натуральный логарифм.

math.log1p(X) — натуральный логарифм (1 + X). При X → 0 точнее, чем math.log(1+X).

math.log10(X) — логарифм X по основанию 10.

math.log2(X) — логарифм X по основанию 2.

math.pow(X, Y) — XY.

math.sqrt(X) — квадратный корень из X.

math.acos(X) — арккосинус X. В радианах.

math.asin(X) — арксинус X. В радианах.

math.atan(X) — арктангенс X. В радианах.

math.atan2(Y, X) — арктангенс Y/X. В радианах. С учетом четверти, в которой находится точка (X, Y).

math.cos(X) — косинус X (X указывается в радианах).

math. sin(X) — синус X (X указывается в радианах).

math.tan(X) — тангенс X (X указывается в радианах).

math.hypot(X, Y) — вычисляет гипотенузу треугольника с катетами X и Y (math.sqrt(x * x + y * y)).

math.degrees(X) — конвертирует радианы в градусы.

math.radians(X) — конвертирует градусы в радианы.

math.cosh(X) — вычисляет гиперболический косинус.

math.sinh(X) — вычисляет гиперболический синус.

math.tanh(X) — вычисляет гиперболический тангенс.

math.acosh(X) — вычисляет обратный гиперболический косинус.

math.asinh(X) — вычисляет обратный гиперболический синус.

math.atanh(X) — вычисляет обратный гиперболический тангенс.

math.erf(X) — функция ошибок.

math.erfc(X) — дополнительная функция ошибок (1 — math.erf(X)).

math.gamma(X) — гамма-функция X.

math.lgamma(X) — натуральный логарифм гамма-функции X.

math. pi — pi = 3,1415926…

math.e — e = 2,718281…

Для вставки кода на Python в комментарий заключайте его в теги <pre><code>Ваш код</code></pre>

Документация JDK 19 — Главная

  1. Главная
  2. Ява
  3. Java SE
  4. 19

Обзор

  • Прочтите меня
  • Примечания к выпуску
  • Что нового
  • Руководство по миграции
  • Загрузить JDK
  • Руководство по установке
  • Формат строки версии

Инструменты

  • Технические характеристики инструментов JDK
  • Руководство пользователя JShell
  • Руководство по JavaDoc
  • Руководство пользователя средства упаковки

Язык и библиотеки

  • Обновления языка
  • Основные библиотеки
  • HTTP-клиент JDK
  • Учебники по Java
  • Модульный JDK
  • Руководство программиста API бортового регистратора
  • Руководство по интернационализации

Технические характеристики

  • Документация API
  • Язык и ВМ
  • Имена стандартных алгоритмов безопасности Java
  • банок
  • Собственный интерфейс Java (JNI)
  • Инструментальный интерфейс JVM (JVM TI)
  • Сериализация
  • Проводной протокол отладки Java (JDWP)
  • Спецификация комментариев к документации для стандартного доклета
  • Прочие характеристики

Безопасность

  • Руководство по безопасному кодированию
  • Руководство по безопасности

Виртуальная машина HotSpot

  • Руководство по виртуальной машине Java
  • Настройка сборки мусора

Управление и устранение неполадок

  • Руководство по устранению неполадок
  • Руководство по мониторингу и управлению
  • Руководство по JMX

Client Technologies

  • Руководство по специальным возможностям Java

4 способа, как числа с плавающей запятой могут испортить ваши деньги Математика

← Назад в блог Главная

Методы разработки

Автор: Патрик Бэкон

14 августа 2014 г.

Напоминание тем, кто давно усвоил этот урок, и предупреждение тем, кто еще не усвоил его для себя: *Не используйте числа с плавающей запятой для денег*.

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

Отличный «Ответ StackOverflow»: http://stackoverflow.com/a/3730040, в котором объясняется проблема:

bq. Представление денег в виде двойного числа или числа с плавающей запятой, вероятно, поначалу будет выглядеть хорошо, так как программное обеспечение округляет крошечные ошибки, но по мере того, как вы выполняете больше сложений, вычитаний, умножений и делений неточных чисел, вы будете терять все больше и больше точности по мере добавления ошибок. вверх. Это делает числа с плавающей запятой и двойные числа непригодными для работы с деньгами, где требуется идеальная точность для множителей степеней с основанием 10.

h3. Что может пойти не так

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

h4. 1. Во время расчетов

Для обеспечения требуемой точности финансовых расчетов наилучшим вариантом (по моему опыту) является использование встроенного десятичного типа, если он предусмотрен в вашем языке (например, в Java и Ruby есть класс @BigDecimal@, в C# — класс @BigDecimal@). тип @decimal@).

Рубиновый:

сумма = BigDecimal.new ("5,99")
 

h4. 2. Во время хранения

Чтобы обеспечить точное представление суммы в валюте в базе данных, используйте что-то вроде столбца @DECIMAL@ в MySQL. Один «ответ StackOverflow»: http://stackoverflow.com/a/13030389указывает, что @DECIMAL(15,2)@ — правильный путь. В PostgreSQL даже есть числовой тип «money»:http://www.postgresql.org/docs/9.1/static/datatype-money.html#DATATYPE-MONEY-TABLE, который можно использовать для столбца.

Если вы создаете свою схему с помощью миграции Rails, вы можете указать десятичный столбец следующим образом:

create_table : запись сделать |t|
  t.decimal :количество, точность: 15, масштаб: 2
конец
 

Как указано в этом сообщении «Десятичные числа в Rails и MySQL»: http://torontoprogrammer.ca/2010/05/decimal-numbers-in-rails-and-mysql/, если вы забудете указать точность и масштаб , Rails по умолчанию (для базы данных MySQL) будет иметь точность 10 и масштаб 0. Как указано в сообщении, это означает, что @15.37@ будет храниться в базе данных как @15@!

h4. 3. Во время извлечения

Теперь, когда база данных точно хранит денежные значения, вы не хотите ничего делать, чтобы потерять эту точность при извлечении из нее значений. В приложении Rails вам не нужно беспокоиться об этом, потому что ActiveRecord автоматически использует @BigDecimal@ для столбцов типа @DECIMAL@. Но если вы используете альтернативный ORM, такой как «DataMapper»: http://datamapper.org, вам нужно убедиться, что вы используете @Decimal@ для своих свойств, а не @Float@.

Такая же возможность для ошибки возникает в ORM на большинстве языков. Например, при использовании Hibernate в Java: http://hibernate.org/ ORM вам нужно указать переменную как @BigDecimal@, а не как @Float@ или @Double@.

Такая же бдительность необходима, когда ORM вообще не используется. Недавно я обнаружил проблему в коде отчетов Java, который использовал прямые вызовы JDBC и извлекал значение цены из базы данных с помощью @rset.getDouble(«price»)@ вместо @rset.getBigDecimal(«price»)@.

h4. 4. Перед отображением

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

Вот, казалось бы, правильное суммирование значений BigDecimal из программы на Ruby:

  subtotals.inject(0.0) сделать |итого, знач.|
    итог += знач.
  конец
 

Но при указании этого начального значения @0.0@ все BigDecimal преобразуются в числа с плавающей запятой, что может привести к ошибкам. Вместо этого он должен выглядеть так:

.
  subtotals.inject(BigDecimal.new(0)) do |total, val|
    итог += знач.
  конец
 

Любой язык, который будет молча преобразовывать десятичный тип в число с плавающей запятой/двойной, подвержен этой простой ошибке.

h3. Ресурсы

Для подробного объяснения/анализа того, почему числа с плавающей запятой неприемлемы для представления денег, прочтите их:

* «Почему бы не использовать Double или Float для представления валюты»: http://stackoverflow.