Автор оригинала: 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
Смотрите также
- стандартная библиотечная документация для десятичных чисел
- Заметки о переносе Python 2 на 3 для десятичных чисел
- Wikipedia: Floating Point – статья о представлениях с плавающей запятой и арифметике.
- Арифметика с плавающей запятой: проблемы и ограничения – статья из учебника Python, описывающая проблемы с математическим представлением с плавающей запятой.