Python 定点的十进制上下文
Python decimal context for fixed point
我想计算小数点前3位和小数点后2位的数字(当然2和3是可以配置的)。我认为通过示例来解释是最容易的:
0.01和999.99是正数的下限和上限。当然也有0.00,还有-999.99到-0.01的负数。每两个连续数字之间的距离为0.01.
7.80 + 1.20 应该是9.00,999.00 + 1.00 应该是OverflowError。 0.20 * 0.40 应该是 0.08,0.34 * 0.20 应该是 0.07(这可以设置一个标志来指示它是四舍五入的,但它不能引发任何异常)。 0.34 * 0.01 应为 0.00(与上一个条件相同)。
其实我想要"ints"从0到99999,只是在第三位后面写一个点,乘的时候缩小100倍,除的时候放大100倍。应该可以找到准确的上下文,对吧?
问题是,我找不到 Emin、Emax、clamp 和 prec 的正确设置来满足我的要求。例如,我尝试将 Emin 和 Emax 设置为 0,但这引发了太多 InvalidOperations。我唯一知道的是四舍五入应该是 ROUND_HALF_EVEN。 :-)
来自文档:
Q. Once I have valid two place inputs, how do I maintain that invariant throughout an application?
A. Some operations like addition, subtraction, and multiplication by an integer will automatically preserve fixed point. Others operations, like division and non-integer multiplication, will change the number of decimal places and need to be followed-up with a quantize() step:
>>> 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')
In developing fixed-point applications, it is convenient to define functions to handle the quantize() step:
>>> 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")
创建定点小数Class
似乎将小数模块修改为定点数(以丢失浮点小数为代价)出奇地容易。这是因为 Decimal
class 在模块 decimal
中被全局名称引用。我们可以插入向下兼容的 class,一切都会正常进行。首先,你需要阻止 python 导入 C _decimal
模块,并使其使用 decimal
模块的纯 python 实现(这样我们就可以重写一个私有方法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
主要十进制class
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")
使用可自定义的上下文
上下文 class 用于跟踪用于控制如何创建新小数的变量。这样每个程序甚至线程都可以设置它想要用于其小数的小数位数。修改 Context
class 有点啰嗦。下面是创建兼容 Context
.
的完整 class
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")
我想计算小数点前3位和小数点后2位的数字(当然2和3是可以配置的)。我认为通过示例来解释是最容易的:
0.01和999.99是正数的下限和上限。当然也有0.00,还有-999.99到-0.01的负数。每两个连续数字之间的距离为0.01.
7.80 + 1.20 应该是9.00,999.00 + 1.00 应该是OverflowError。 0.20 * 0.40 应该是 0.08,0.34 * 0.20 应该是 0.07(这可以设置一个标志来指示它是四舍五入的,但它不能引发任何异常)。 0.34 * 0.01 应为 0.00(与上一个条件相同)。
其实我想要"ints"从0到99999,只是在第三位后面写一个点,乘的时候缩小100倍,除的时候放大100倍。应该可以找到准确的上下文,对吧?
问题是,我找不到 Emin、Emax、clamp 和 prec 的正确设置来满足我的要求。例如,我尝试将 Emin 和 Emax 设置为 0,但这引发了太多 InvalidOperations。我唯一知道的是四舍五入应该是 ROUND_HALF_EVEN。 :-)
来自文档:
Q. Once I have valid two place inputs, how do I maintain that invariant throughout an application?
A. Some operations like addition, subtraction, and multiplication by an integer will automatically preserve fixed point. Others operations, like division and non-integer multiplication, will change the number of decimal places and need to be followed-up with a quantize() step:
>>> 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')
In developing fixed-point applications, it is convenient to define functions to handle the quantize() step:
>>> 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")
创建定点小数Class
似乎将小数模块修改为定点数(以丢失浮点小数为代价)出奇地容易。这是因为 Decimal
class 在模块 decimal
中被全局名称引用。我们可以插入向下兼容的 class,一切都会正常进行。首先,你需要阻止 python 导入 C _decimal
模块,并使其使用 decimal
模块的纯 python 实现(这样我们就可以重写一个私有方法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
主要十进制class
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")
使用可自定义的上下文
上下文 class 用于跟踪用于控制如何创建新小数的变量。这样每个程序甚至线程都可以设置它想要用于其小数的小数位数。修改 Context
class 有点啰嗦。下面是创建兼容 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")