Рубрики
Без рубрики

decimal – математика с фиксированной и плавающей запятой

Автор оригинала: Doug Hellmann.

Цель:

Десятичная арифметика с использованием чисел с фиксированной и плавающей запятой

Модуль decimal реализует арифметику с фиксированной и плавающей запятой, используя модель, знакомую большинству людей, а не версию с плавающей запятой IEEE, реализованную большинством компьютерного оборудования и знакомую программистам. Экземпляр Decimal может точно представлять любое число, округлять в большую или меньшую сторону и применять ограничение на количество значащих цифр.

Десятичный

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

decimal_create.py

import decimal

fmt  '{0:<25} {1:<25}'

print(fmt.format('Input', 'Output'))
print(fmt.format('-' * 25, '-' * 25))

# Integer
print(fmt.format(5, decimal.Decimal(5)))

# String
print(fmt.format('3.14', decimal.Decimal('3.14')))

# Float
f  0.1
print(fmt.format(repr(f), decimal.Decimal(str(f))))
print('{:<0.23g} {:<25}'.format(
    f,
    str(decimal.Decimal.from_float(f))[:25])
)

Значение с плавающей запятой 0,1 не представлено как точное значение в двоичном формате, поэтому представление в виде float отличается от значения Decimal . Полное строковое представление усекается до 25 символов в последней строке этого вывода.

$ python3 decimal_create.py

Input                     Output
------------------------- -------------------------
5                         5
3.14                      3.14
0.1                       0.1
0.10000000000000000555112 0.10000000000000000555111

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

decimal_tuple.py

import decimal

# Tuple
t  (1, (1, 1), -2)
print('Input  :', t)
print('Decimal:', decimal.Decimal(t))

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

$ python3 decimal_tuple.py

Input  : (1, (1, 1), -2)
Decimal: -0.11

Форматирование

Decimal отвечает на протокол форматирования строк Python, используя тот же синтаксис и варианты, как и другие числовые типы.

decimal_format.py

import decimal

d  decimal.Decimal(1.1)
print('Precision:')
print('{:.1}'.format(d))
print('{:.2}'.format(d))
print('{:.3}'.format(d))
print('{:.18}'.format(d))

print('\nWidth and precision combined:')
print('{:5.1f} {:5.1g}'.format(d, d))
print('{:5.2f} {:5.2g}'.format(d, d))
print('{:5.2f} {:5.2g}'.format(d, d))

print('\nZero padding:')
print('{:05.1}'.format(d))
print('{:05.2}'.format(d))
print('{:05.3}'.format(d))

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

$ python3 decimal_format.py

Precision:
1
1.1
1.10
1.10000000000000009

Width and precision combined:
  1.1     1
 1.10   1.1
 1.10   1.1

Zero padding:
00001
001.1
01.10

Арифметика

Decimal перегружает простые арифметические операторы, поэтому экземплярами можно управлять почти так же, как встроенными числовыми типами.

decimal_operators.py

import decimal

a  decimal.Decimal('5.1')
b  decimal.Decimal('3.14')
c  4
d  3.14

print('a     =', repr(a))
print('b     =', repr(b))
print('c     =', repr(c))
print('d     =', repr(d))
print()

print('a + b =', a + b)
print('a - b =', a - b)
print('a * b =', a * b)
print('a / b =', a / b)
print()

print('a + c =', a + c)
print('a - c =', a - c)
print('a * c =', a * c)
print('a / c =', a / c)
print()

print('a + d =', end' ')
try:
    print(a + d)
except TypeError as e:
    print(e)

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

$ python3 decimal_operators.py

a     = Decimal('5.1')
b     = Decimal('3.14')
c     = 4
d     = 3.14

a + b = 8.24
a - b = 1.96
a * b = 16.014
a / b = 1.624203821656050955414012739

a + c = 9.1
a - c = 1.1
a * c = 20.4
a / c = 1.275

a + d = unsupported operand type(s) for +: 'decimal.Decimal' and
 'float'

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

Особые ценности

Помимо ожидаемых числовых значений, Decimal может представлять несколько специальных значений, включая положительные и отрицательные значения для бесконечности, «не числа» и нуля.

decimal_special.py

import decimal

for value in ['Infinity', 'NaN', '0']:
    print(decimal.Decimal(value), decimal.Decimal('-' + value))
print()

# Math with infinity
print('Infinity + 1:', (decimal.Decimal('Infinity') + 1))
print('-Infinity + 1:', (decimal.Decimal('-Infinity') + 1))

# Print comparing NaN
print(decimal.Decimal('NaN')  decimal.Decimal('Infinity'))
print(decimal.Decimal('NaN')  decimal.Decimal(1))

Добавление к бесконечным значениям возвращает еще одно бесконечное значение. Сравнение на равенство с NaN всегда возвращает false, а сравнение на неравенство всегда возвращает true. Сравнение порядка сортировки с NaN не определено и приводит к ошибке.

$ python3 decimal_special.py

Infinity -Infinity
NaN -NaN
0 -0

Infinity + 1: Infinity
-Infinity + 1: -Infinity
False
True

Контекст

До сих пор во всех примерах использовалось поведение по умолчанию модуля decimal . Можно переопределить такие параметры, как поддерживаемая точность, способ округления, обработка ошибок и т. Д., Используя контекст . Контексты могут применяться ко всем экземплярам Decimal в потоке или локально в небольшой области кода.

Текущий контекст

Чтобы получить текущий глобальный контекст, используйте getcontext .

decimal_getcontext.py

import decimal

context  decimal.getcontext()

print('Emax     =', context.Emax)
print('Emin     =', context.Emin)
print('capitals =', context.capitals)
print('prec     =', context.prec)
print('rounding =', context.rounding)
print('flags    =')
for f, v in context.flags.items():
    print('  {}: {}'.format(f, v))
print('traps    =')
for t, v in context.traps.items():
    print('  {}: {}'.format(t, v))

В этом примере сценария показаны общедоступные свойства Context .

$ python3 decimal_getcontext.py

Emax     = 999999
Emin     = -999999
capitals = 1
prec     = 28
rounding = ROUND_HALF_EVEN
flags    =
  : False
  : False
  : False
  : False
  : False
  : False
  : False
  : False
  : False
traps    =
  : True
  : False
  : True
  : True
  : False
  : False
  : False
  : False
  : False

Точность

Атрибут контекста prec управляет точностью, поддерживаемой для новых значений, созданных в результате арифметических действий. Буквальные значения сохраняются, как описано.

decimal_precision.py

import decimal

d  decimal.Decimal('0.123456')

for i in range(1, 5):
    decimal.getcontext().prec  i
    print(i, ':', d, d * 1)

Чтобы изменить точность, присвойте новое значение от 1 до decimal.MAX_PREC непосредственно атрибуту.

$ python3 decimal_precision.py

1 : 0.123456 0.1
2 : 0.123456 0.12
3 : 0.123456 0.123
4 : 0.123456 0.1235

Округление

Есть несколько вариантов округления для сохранения значений в пределах желаемой точности.

ROUND_CEILING

Всегда округляйте вверх до бесконечности.

ОКРУГЛИТЬ ВНИЗ

Всегда округляется к нулю.

ROUND_FLOOR

Всегда округляйте до отрицательной бесконечности.

ROUND_HALF_DOWN

Округляет от нуля, если последняя значащая цифра больше, чем или равно 5, в противном случае – к нулю.

ROUND_HALF_EVEN

Нравиться

ROUND_HALF_DOWN

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

ROUND_HALF_UP

Нравиться

ROUND_HALF_DOWN

кроме случаев, когда последняя значащая цифра равно 5, значение округляется от нуля.

ОКРУГЛЯТЬ

Округлить от нуля.

ROUND_05UP

Округлить от нуля, если последняя цифра

или же

, иначе к нулю.

decimal_rounding.py

import decimal

context  decimal.getcontext()

ROUNDING_MODES  [
    'ROUND_CEILING',
    'ROUND_DOWN',
    'ROUND_FLOOR',
    'ROUND_HALF_DOWN',
    'ROUND_HALF_EVEN',
    'ROUND_HALF_UP',
    'ROUND_UP',
    'ROUND_05UP',
]

header_fmt  '{:10} ' + ' '.join(['{:^8}'] * 6)

print(header_fmt.format(
    ' ',
    '1/8 (1)', '-1/8 (1)',
    '1/8 (2)', '-1/8 (2)',
    '1/8 (3)', '-1/8 (3)',
))
for rounding_mode in ROUNDING_MODES:
    print('{0:10}'.format(rounding_mode.partition('_')[-1]),
          end' ')
    for precision in [1, 2, 3]:
        context.prec  precision
        context.rounding  getattr(decimal, rounding_mode)
        value  decimal.Decimal(1) / decimal.Decimal(8)
        print('{0:^8}'.format(value), end' ')
        value  decimal.Decimal(-1) / decimal.Decimal(8)
        print('{0:^8}'.format(value), end' ')
    print()

Эта программа показывает эффект округления одного и того же значения до разных уровней точности с использованием разных алгоритмов.

$ python3 decimal_rounding.py

           1/8 (1)  -1/8 (1) 1/8 (2)  -1/8 (2) 1/8 (3)  -1/8 (3)
CEILING      0.2      -0.1     0.13    -0.12    0.125    -0.125

DOWN         0.1      -0.1     0.12    -0.12    0.125    -0.125

FLOOR        0.1      -0.2     0.12    -0.13    0.125    -0.125

HALF_DOWN    0.1      -0.1     0.12    -0.12    0.125    -0.125

HALF_EVEN    0.1      -0.1     0.12    -0.12    0.125    -0.125

HALF_UP      0.1      -0.1     0.13    -0.13    0.125    -0.125

UP           0.2      -0.2     0.13    -0.13    0.125    -0.125

05UP         0.1      -0.1     0.12    -0.12    0.125    -0.125

Местный контекст

Контекст можно применить к блоку кода с помощью оператора with .

decimal_context_manager.py

import decimal

with decimal.localcontext() as c:
    c.prec  2
    print('Local precision:', c.prec)
    print('3.14 / 3 =', (decimal.Decimal('3.14') / 3))

print()
print('Default precision:', decimal.getcontext().prec)
print('3.14 / 3 =', (decimal.Decimal('3.14') / 3))

Context поддерживает API диспетчера контекста, используемый with , поэтому настройки применяются только внутри блока.

$ python3 decimal_context_manager.py

Local precision: 2
3.14 / 3 = 1.0

Default precision: 28
3.14 / 3 = 1.046666666666666666666666667

Контекст для каждого экземпляра

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

decimal_instance_context.py

import decimal

# Set up a context with limited precision
c  decimal.getcontext().copy()
c.prec  3

# Create our constant
pi  c.create_decimal('3.1415')

# The constant value is rounded off
print('PI    :', pi)

# The result of using the constant uses the global context
print('RESULT:', decimal.Decimal('2.01') * pi)

Это позволяет приложению, например, выбирать точность постоянных значений отдельно от точности пользовательских данных.

$ python3 decimal_instance_context.py

PI    : 3.14
RESULT: 6.3114

Потоки

«Глобальный» контекст на самом деле является локальным для потока, поэтому каждый поток потенциально может быть настроен с использованием разных значений.

decimal_thread_context.py

import decimal
import threading
from queue import PriorityQueue


class Multiplier(threading.Thread):
    def __init__(self, a, b, prec, q):
        self.a  a
        self.b  b
        self.prec  prec
        self.q  q
        threading.Thread.__init__(self)

    def run(self):
        c  decimal.getcontext().copy()
        c.prec  self.prec
        decimal.setcontext(c)
        self.q.put((self.prec, a * b))


a  decimal.Decimal('3.14')
b  decimal.Decimal('1.234')
# A PriorityQueue will return values sorted by precision,
# no matter what order the threads finish.
q  PriorityQueue()
threads  [Multiplier(a, b, i, q) for i in range(1, 6)]
for t in threads:
    t.start()

for t in threads:
    t.join()

for i in range(5):
    prec, value  q.get()
    print('{}  {}'.format(prec, value))

В этом примере создается новый контекст с использованием указанного, а затем устанавливается в каждом потоке.

$ python3 decimal_thread_context.py

1  4
2  3.9
3  3.87
4  3.875
5  3.8748

Смотрите также