Из документации:
В. Если у меня есть допустимые двухзначные входные данные, как мне поддерживать этот инвариант во всем приложении?
A. Некоторые операции, такие как сложение, вычитание и умножение на целое число, автоматически сохраняют фиксированную точку. Другие операции, такие как деление и нецелочисленное умножение, изменят количество десятичных знаков и требуют выполнения шага quantize ():
>>> TWOPLACES = Decimal(10) ** -2 # same as Decimal('0.01')
>>> a = Decimal('102.72') # Initial fixed-point values
>>> b = Decimal('3.17')
>>> a + b # Addition preserves fixed-point
Decimal('105.89')
>>> a - b
Decimal('99.55')
>>> a * 42 # So does integer multiplication
Decimal('4314.24')
>>> (a * b).quantize(TWOPLACES) # Must quantize non-integer multiplication
Decimal('325.62')
>>> (b / a).quantize(TWOPLACES) # And quantize division
Decimal('0.03')
При разработке приложений с фиксированной точкой удобно определять функции для обработки шага quantize ():
>>> def mul(x, y, fp=TWOPLACES):
... return (x * y).quantize(fp)
>>> def div(x, y, fp=TWOPLACES):
... return (x / y).quantize(fp)
>>> mul(a, b) # Automatically preserve fixed-point
Decimal('325.62')
>>> div(b, a)
Decimal('0.03')
Похоже, решение состоит в том, чтобы установить точность до 5 и Emax на 2 и использовать эти функции квантования.
con = decimal.getcontext()
con.prec = 5
con.Emax = 2
con.Emin = 0
try:
Decimal(1) * 1000
except decimal.Overflow as e:
print(e)
else:
assert False
assert Decimal("0.99") * 1000 == Decimal("990.00")
assert div(Decimal(1), 3) == Decimal("0.33")
Создание десятичного класса с фиксированной запятой
Кажется, что на удивление легко изменить десятичный модуль, чтобы он стал фиксированной точкой (за счет потери десятичных знаков с плавающей запятой). Это связано с тем, что на класс Decimal
ссылается глобальное имя в модуле decimal
. Мы можем вставить наш класс, совместимый с вниз, и все будет работать нормально. Сначала вам нужно запретить python импортировать модуль C _decimal
и заставить его использовать реализацию модуля decimal
на чистом питоне (чтобы мы могли переопределить частный метод Decimal
). Как только вы это сделаете, вам нужно переопределить только один метод - _fix
. Он вызывается для каждого нового Decimal
, которое создается, для которого возможно, что он может не соответствовать текущему десятичному контексту.
настройка модуля
# setup python to not import _decimal (c implementation of Decimal) if present
import sys
if "_decimal" in sys.modules or "decimal" in sys.modules:
raise ImportError("fixedpointdecimal and the original decimal module do not work"
" together")
import builtins
_original_import = __import__
def _import(name, *args, **kwargs):
if name == "_decimal":
raise ImportError
return _original_import(name, *args, **kwargs)
builtins.__import__ = _import
# import pure-python implementation of decimal
import decimal
# clean up
builtins.__import__ = _original_import # restore original __import__
del sys, builtins, _original_import, _import # clean up namespace
основной десятичный класс
from decimal import *
class FixedPointDecimal(Decimal):
def _fix(self, context):
# always fit to 2dp
return super()._fix(context)._rescale(-2, context.rounding)
# use context to find number of decimal places to use
# return super()._fix(context)._rescale(-context.decimal_places, context.rounding)
# setup decimal module to use FixedPointDecimal
decimal.Decimal = FixedPointDecimal
Decimal = FixedPointDecimal
тесты
getcontext().prec = 5
getcontext().Emax = 2
a = Decimal("0.34")
b = Decimal("0.20")
assert a * b == Decimal("0.07")
Использование настраиваемого контекста
Класс контекста используется для отслеживания используемых переменных, управляющих созданием новых десятичных знаков. Таким образом, каждая программа или даже поток смогут установить количество десятичных знаков, которое они хотят использовать для своих десятичных знаков. Изменение класса Context
немного сложнее. Ниже приведен полный класс для создания совместимого Context
.
class FixedPointContext(Context):
def __init__(self, prec=None, rounding=None, Emin=None, Emax=None,
capitals=None, clamp=None, flags=None, traps=None,
_ignored_flags=None, decimal_places=None):
super().__init__(prec, rounding, Emin, Emax, capitals, clamp, flags,
traps, _ignored_flags)
try:
dc = DefaultContext
except NameError:
pass
self.decimal_places = decimal_places if decimal_places is not None else dc.decimal_places
def __setattr__(self, name, value):
if name == "decimal_places":
object.__setattr__(self, name, value)
else:
super().__setattr__(name, value)
def __reduce__(self):
flags = [sig for sig, v in self.flags.items() if v]
traps = [sig for sig, v in self.traps.items() if v]
return (self.__class__,
(self.prec, self.rounding, self.Emin, self.Emax,
self.capitals, self.clamp, flags, traps, self._ignored_flags,
self.decimal_places))
def __repr__(self):
"""Show the current context."""
s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)s, '
'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d, '
'clamp=%(clamp)d, decimal_places=%(decimal_places)d'
% vars(self))
names = [f.__name__ for f, v in self.flags.items() if v]
s.append('flags=[' + ', '.join(names) + ']')
names = [t.__name__ for t, v in self.traps.items() if v]
s.append('traps=[' + ', '.join(names) + ']')
return ', '.join(s) + ')'
def _shallow_copy(self):
"""Returns a shallow copy from self."""
nc = Context(self.prec, self.rounding, self.Emin, self.Emax,
self.capitals, self.clamp, self.flags, self.traps,
self._ignored_flags, self.decimal_places)
return nc
def copy(self):
"""Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.Emin, self.Emax,
self.capitals, self.clamp,
self.flags.copy(), self.traps.copy(),
self._ignored_flags, self.decimal_places)
return nc
__copy__ = copy
# reinitialise default context
DefaultContext = FixedPointContext(decimal_places=2)
# copy changes over to decimal module
decimal.Context = FixedPointContext
decimal.DefaultContext = DefaultContext
Context = FixedPointContext
# test
decimal.getcontext().decimal_places = 1
decimal.getcontext().prec = 5
decimal.getcontext().Emax = 2
a = Decimal("0.34")
b = Decimal("0.20")
assert a * b == Decimal("0.1")
person
Dunes
schedule
03.03.2015
12345.0
, и0.12345
), я думаю, что лучшим способом было бы создать для этого собственный класс на основеint
. - person Tim Pietzcker   schedule 03.03.2015decimal
, несмотря на много дней, потраченных на эту проблему. . - person Gustavo Bezerra   schedule 22.06.2018