【问题标题】:Handling very very small numbers in Python在 Python 中处理非常小的数字
【发布时间】:2019-11-09 22:50:49
【问题描述】:

我需要乘以 1e6 数量级的 0.01。预期结果为1e-100000000。显然,典型的浮点算法无法处理这个问题。

在网上做一些研究,我发现decimal library 似乎可以解决这个问题。然而,它似乎有一些限制,使其无法满足我的需求:

>>> Decimal('.01')**Decimal('1e5') # Seems to handle this
Decimal('1E-200000')
>>> Decimal('.01')**Decimal('1e5')*Decimal('1E200000') # Yeah! It works!
Decimal('1')
>>> Decimal('.01')**Decimal('1e6') # This result is strange...
Decimal('0E-1000026')
>>> Decimal('.01')**Decimal('1e6')*Decimal('0E1000026') # Wrong result
Decimal('0')

有人知道解决办法吗?

【问题讨论】:

  • 您可以始终将指数保持在一个单独的 int 中,并将乘积保持在 0.5 和 1.0 之间。有很多可能性,这只是一个例子。例如,请参阅math.frexp()。也许记录日志是最简单的事情,这在一定程度上取决于您打算如何处理结果。

标签: python math decimal underflow


【解决方案1】:

你的结果不正确,因为小数也有精度(小数是定点数学),所以你也会在这里遇到下溢问题:

Decimal('.01')**Decimal('1e6')

十进制('0E-1000026')

但是:

getcontext().prec = 1000000000   # sets precision to 1000000000
Decimal('.01')**Decimal('1e6')

十进制('1E-2000000')

您可以通过手动设置精度(如上例)或手动计算幂来解决您的问题,例如:

Decimal('.01')**Decimal('1e6')

可以转换成

Decimal('1e-2') ** Decimal('1e6')

后来到

1 ** ((-2) ** 1e6) = 1 ** (-2000000)

Decimal module documentation

【讨论】:

    【解决方案2】:

    为什么不用对数?

    你想计算:

    RESULT  = x1 * x2 * x3 * x4 ... * xn
    

    表示为:

    ln(RESULT) = ln(x1) + ln(x2) + ln(x3) + ln(x4) ... + ln(xn)
    

    如果您存储它们的自然对数,非常小的正数可以很好地存储到浮点数中:

    ln(0.000001) ≈ -13.81551
    

    不是存储数字本身,而是存储值的日志。

    假设您将ln(0.0000011) 添加到自身10^6 次。你得到大约-13815510.558。与0.000001^(10^6) 相比,float 的精度损失更少

    无论你最终得到什么数字,你都知道你的结果只是数字e 的幂。例如RESULT = e^-13815510.558

    您可以使用下面的代码:

    import math
    
    class TinyNum:
        def __init__(self, other=None, *, pow=None):
            """
            x = TinyNum(0.0000912922)
            x = TinyNum("0.12345")     # strings are okay too
            x = TinyNum(pow = -110)    # e^-110
            y = TinyNum(x)             # copy constructor
            """
            if other:
                if isinstance(other, type(self)):
                    self._power = other._power
                else:
                    self._power = math.log(float(str(other)))
            else: # other == None
                self._power = float(str(pow))
    
        def __str__(self):
            return "e^"+str(self._power)
    
        def __mul__(lhs, rhs):
            rhs = type(lhs)(rhs)
            return type(lhs)(pow=lhs._power + rhs._power)
    
        def __rmul__(rhs, lhs):
            lhs = type(rhs)(lhs)
            return type(rhs)(pow=lhs._power + rhs._power)
    
        def __imul__(total, margin):
            total._power = total._power + type(total)(margin)._power
    
    
    lyst = [
        0.00841369,
        0.004766949,
        0.003188046,
        0.002140916,
        0.004780032
    ]
    
    sneaky_lyst = map(TinyNum, lyst)
    
    print(math.prod(sneaky_lyst))
    

    打印到控制台的消息是:

    e^-27.36212057035477
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-07
      • 1970-01-01
      • 2013-08-05
      • 1970-01-01
      相关资源
      最近更新 更多