用巨大的中间值计算总和

时间:2013-04-17 11:26:02

标签: python math

我想计算

enter image description here

尽可能准确地获取n最多1000000的值。这是一些示例代码。

from __future__ import division
from scipy.misc import comb
def M(n):
    return sum(comb(n,k,exact=True)*(1/n)*(1-k/n)**(2*n-k)*(k/n)**(k-1) for k in xrange(1,n+1))
for i in xrange(1,1000000,100):
    print i,M(i)

第一个问题是OverflowError: long int too large to convert时我n = 1101会浮动。这是因为comb(n,k,exact=True)太大而无法转换为浮点数。然而,最终结果始终是0.159附近的数字。

我在How to compute sum with large intermediate values问了一个相关的问题但是这个问题因三个主要原因而有所不同。

  1. 我想要计算的公式不同会导致不同的问题。
  2. 之前提出的使用exact = True的解决方案在这里没有帮助,这可以在我给出的示例中看到。编写我自己的梳子实现也不会起作用,因为我仍然需要执行浮点除法。
  3. 我需要计算比以前更大的值的答案,这会导致新的问题。我怀疑如果不以一种聪明的方式编写总和就无法完成。
  4. 不崩溃的解决方案是使用

    from fractions import Fraction
    def M2(n):
        return sum(comb(n,k,exact=True)*Fraction(1,n)*(1-Fraction(k,n))**(2*n-k)*Fraction(k,n)**(k-1) for k in xrange(1,n+1))
    for i in xrange(1,1000000,100):
        print i, M2(i)*1.0
    

    不幸的是,现在这么慢,我在合理的时间内没有得到n=1101的答案。

    所以第二个问题是如何让它足够快以完成大n

3 个答案:

答案 0 :(得分:4)

您可以使用对数变换计算每个加数,该变换分别用加法,减法和乘法替换乘法,除法和乘幂。

def summand(n,k):
    lk=log(k)
    ln=log(n)
    a=(lk-ln)*(k-1)
    b=(log(n-k)-ln)*(2*n-k)
    c=-ln
    d=sum(log(x) for x in xrange(n-k+1,n+1))-sum(log(x) for x in xrange(1,k+1))
    return exp(a+b+c+d)

def M(n):
    return sum(summand(n,k) for k in xrange(1,n))

注意,当k = n时,summand将为零,所以我不计算它,因为对数将是未定义的。

答案 1 :(得分:3)

您可以使用gmpy2。它具有任意精度浮点运算和大指数范围。

from __future__ import division
from gmpy2 import comb,mpfr,fsum

def M(n):
    return fsum(comb(n,k)*(mpfr(1)/n)*(mpfr(1)-mpfr(k)/n)**(mpfr(2)*n-k)*(mpfr(k)/n)**(k-1) for k in xrange(1,n+1))
for i in xrange(1,1000000,100):
    print i,M(i)

以下是输出的摘录:

2001 0.15857490038127975
2101 0.15857582611615381
2201 0.15857666768820194
2301 0.15857743607577454
2401 0.15857814042739268
2501 0.15857878842787806
2601 0.15857938657957615

免责声明:我维持gmpy2。

答案 2 :(得分:2)

一种相当残酷的方法是计算所有因子,然后以一种结果保持在1.0左右的方式多次计算(Python 3.x):

def M(n):
    return sum(summand(n, k) for k in range(1, n + 1))

def f1(n, k):
    for i in range(k - 1):
        yield k
    for i in range(k):
        yield n - i

def f2(n, k):
    for i in range(k - 1):
        yield 1 / n
    for i in range(2 * n - k):
        yield 1 - k / n
    yield 1 / n
    for i in range(2, k + 1):
        yield 1 / i

def summand(n, k):
    result = 1.0
    factors1 = f1(n, k)
    factors2 = f2(n, k)
    while True:
        empty1 = False
        for factor in factors1:
            result *= factor
            if result > 1:
                break
        else:
            empty1 = True
        for factor in factors2:
            result *= factor
            if result < 1:
                break
        else:
            if empty1:
                break
    return result

对于M(1101),我得到0.15855899364641846,但需要几秒钟。 M(2000)大约需要14秒,产生0.15857489065619598

(我确信它可以进行优化。)