我为什么要控制计算?

时间:2016-02-24 19:05:41

标签: python cpython

我有一个有循环的函数,我在其中进行除法和乘法运算。最终的答案很容易代表,正如运行的答案一样。

def tie(total):
    count = total / 2
    prob = 1.0
    for i in xrange(1, count + 1):
        i_f = float(i)
        prob *= (count + i_f) / i_f / 4
    return prob

-

tie(4962) == 0.01132634537589437

tie(4964) == inf

编译器是否尝试进行一些优化,按照我似乎指定的顺序执行算术运算,并且该顺序应该是等效的但会导致溢出?

4 个答案:

答案 0 :(得分:4)

您遇到了问题,因为即使tie函数的最终结果在数学上应该在01之间,<循环中的em> intermediate 值变得非常大:对于total = 4962,迭代中途的prob值约为1.5e308几乎相当大到足以溢出Python float。对于total = 4964,中间值确实溢出float,并且自inf次以来任何有限值仍为infinf溢出的1}}一直传播到最终值。

如果您准备接受(相当小)的浮点错误,则根本不需要使用循环来计算此数量:您可以使用lgamma函数来自math模块来计算相关因子的日志。 (您也可以直接使用gamma函数,但这可能也会导致溢出问题。)

这是基于此功能的版本。

from math import lgamma, log, exp

def tie(total):
    count = total / 2
    return exp(lgamma(2*count + 1) - 2*lgamma(count + 1) - count*log(4))

或者,你可以使用纯整数运算(它不会导致溢出)计算2n-choose-n项,并且只在最后一刻产生一个浮点数(除以4**count)。这将比上述效率低,但会给你(在某种意义上)完美的准确性,因为它会给出最接近的可表示的浮点数到确切的答案。这是该版本的样子:

from __future__ import division

def tie(total):
    count = total // 2
    prod = 1
    for i in xrange(1, count+1):
        prod = prod * (count + i) // i
    return prod / 4**count

注意:prod * (count + i) // i中的楼层划分可能看起来不对,但它确实有效:一些基本数论表明,在计算的这一点上,prod * (count + i)必须可以被{{i整除1}},所以进行整数除法是安全的。

最后,只是为了好玩,这里是第三种方法来计算与原始代码在精神上相似的概率,但避免溢出:值prob从{{1}开始并稳步下降到最终值。

1.0

除了免受溢出问题的影响外,此解决方案将比基于整数的解决方案更高效,并且比基于def tie(total): count = total // 2 prob = 1.0 for i in xrange(1, count+1): prob *= (i-0.5) / i return prob 的解决方案更准确。

答案 1 :(得分:1)

prob变得非常大并最终溢出。鉴于名称,您是否希望prob始终介于0和1之间?

答案 2 :(得分:0)

你的prob变量非常大,总数等于4964,溢出了Python最大浮点值sys.float_info

>>> import sys
>>> print(sys.float_info.max)
1.7976931348623157e+308

答案 3 :(得分:0)

你是什么意思&#34;控制计算&#34;?导致溢出的原因是prob越来越大。