Модуль Decimal в Python: быстрая арифметика и округление

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

Содержание

Введение в десятичный модуль Python

Многие десятичные числа не имеют точного представления в двоичном формате с плавающей запятой, например 0,1. Используя эти числа в арифметических операциях, вы получите результат, которого не ожидали. Например:

x = 0.1
y = 0.1
z = 0.1

s = x + y + z

print(s)

Выход:

0.30000000000000004

Результат 0,30000000000000004, а не 0,3.

Чтобы решить эту проблему, используйте класс Decimal из модуля decimal следующим образом:

import decimal
from decimal import Decimal


x = Decimal('0.1')
y = Decimal('0.1')
z = Decimal('0.1')

s = x + y + z

print(s)

Выход:

0.3

Результат соответствует ожиданиям.

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

В отличие от чисел с плавающей запятой, Python точно представляет десятичные числа. И точность переносится в арифметику. Например, следующее выражение возвращает ровно 0,0:

Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')

Десятичный контекст

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

  • Точность во время арифметической операции
  • Алгоритм округления

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

Чтобы получить контекст по умолчанию, вы вызываете функцию getcontext() из decimal модуля:

decimal.getcontext()

Функция getcontext() возвращает контекст по умолчанию, который может быть глобальным или локальным.

Чтобы создать новый контекст, скопированный из другого, используйте функцию localcontext():

decimal.localcontext(ctx=None)

localcontext() возвращает новый контекст, скопированный из контекста ctx, если он указан.

Получив объект контекста, вы можете получить доступ к точности и маршрутизации через свойства prec и rounding соответственно:

  • ctx.pre: получить или установить точность. ctx.pre — это целое число, которое по умолчанию равно 28.
  • ctx.rounding: получить или установить механизм округления. Округление представляет собой строку. По умолчанию установлено значение «ROUND_HALF_EVEN».

Python предоставляет следующие механизмы округления:

Округление Описание
ROUND_UP округлить от нуля
ROUND_DOWN округлить до нуля
ROUND_CEILING округляется до потолка (в сторону положительной бесконечности)
ROUND_FLOOR округляется до пола (в сторону отрицательной бесконечности)
ROUND_HALF_UP округляется до ближайшего значения, от нуля
ROUND_HALF_DOWN до ближайшего значения, до нуля
ROUND_HALF_EVEN округление до ближайшего значения, привязка к четному(наименьшая значащая цифра)

В этом примере показано, как получить точность и округление по умолчанию для контекста по умолчанию:

import decimal

ctx = decimal.getcontext()

print(ctx.prec)
print(ctx.rounding)

Выход:

28
ROUND_HALF_EVEN

В следующем примере показано, как действует механизм округления ROUND_HALF_EVEN:

import decimal
from decimal import Decimal


x = Decimal('2.25')
y = Decimal('3.35')

print(round(x, 1))
print(round(y, 1))

Выход:

2.2
3.4

Если вы измените округление на «ROUND_HALF_UP», вы получите другой результат:

import decimal
from decimal import Decimal


ctx = decimal.getcontext()
ctx.rounding = decimal.ROUND_HALF_UP

x = Decimal('2.25')
y = Decimal('3.35')

print(round(x, 1))
print(round(y, 1))

Выход:

2.3
3.4

В следующем примере показано, как скопировать контекст по умолчанию и изменить округление на «ROUND_HALF_UP»:

import decimal
from decimal import Decimal


x = Decimal('2.25')
y = Decimal('3.35')

with decimal.localcontext() as ctx:
    print('Local context:')
    ctx.rounding = decimal.ROUND_HALF_UP
    print(round(x, 1))
    print(round(y, 1))

print('Global context:')
print(round(x, 1))
print(round(y, 1))

Выход:

Local context:
2.3
3.4
Global context:
2.2
3.4

Обратите внимание, что локальный контекст не влияет на глобальный контекст. После блока with Python использует механизм округления по умолчанию.

Десятичный конструктор Decimal

Конструктор Decimal позволяет вам создать новый объект Decimal на основе значения:

Decimal(value='0', context=None)

Аргументом value может быть целое число, строка, кортеж, число с плавающей запятой или другой объект Decimal. Если вы не укажете аргумент value, по умолчанию он будет равен «0».

Если значение представляет собой кортеж, оно должно состоять из трех компонентов: знака (0 для положительного значения или 1 для отрицательного), кортежа цифр и целочисленного показателя степени:

(sign,(digit1,digit2, digit3,...), exponent)

Например:

3.14 = 314 x 10^-2

Кортеж состоит из трех элементов:

  • знак (0)
  • цифры (3,1,4)
  • показатель степени (-2)

Поэтому вам нужно будет передать в конструктор Decimal следующий кортеж:

import decimal
from decimal import Decimal

x = Decimal((0,(3, 1, 4), -2))
print(x)

Выход:

3.14

Обратите внимание, что точность десятичного контекста влияет только на арифметическую операцию, а не на конструктор Decimal. Например:

import decimal
from decimal import Decimal


decimal.getcontext().prec = 2

pi = Decimal('3.14159')
radius = 1

print(pi)

area = pi * radius * radius
print(area)

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

import decimal
from decimal import Decimal

x = Decimal(0.1)
print(x)

Выход:

0.1000000000000000055511151231257827021181583404541015625

На практике для создания десятичного числа вы будете использовать строку или кортеж.

Десятичные арифметические операции

Некоторые арифметические операторы работают не так, как числа с плавающей запятой или целые числа, например div(//) и mod(%).

Для десятичных чисел оператор // выполняет усеченное деление:

x // y = trunc( x / y)

Класс Decimal предоставляет некоторые математические операции, такие как sqrt и log. Однако он не имеет всех функций, определенных в математическом модуле.

Когда вы используете функции из модуля math для десятичных чисел, Python преобразует объекты Decimal в числа с плавающей запятой перед выполнением арифметических операций. Это приводит к потере точности десятичных объектов.

Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *