繁体   English   中英

固定点的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(与上一个条件相同)。

实际上,我希望从0到99999的“整数”在第三个数字后加一个点,乘以100倍,除以100倍。 应该有可能恰好找到一个上下文,对吗?

问题是,我找不到适合我所需的Emin,Emax,clamp和prec的正确设置。 例如,我尝试将Emin和Emax设置为0,但这引发了太多InvalidOperations。 我唯一知道的是四舍五入应该是ROUND_HALF_EVEN。 :-)

从文档中:

问:一旦有了有效的两个位置输入,如何在整个应用程序中保持不变?

答:一些操作,例如加,减和乘以整数将自动保留定点。 其他操作(例如除法和非整数乘法)将更改小数位数,并且需要在其后执行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模块的纯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

主十进制类

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")

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM