如何在python中将超大数与超小数相乘?

时间:2015-10-12 03:51:22

标签: python numpy

我正在做一些概率计算 在我的任务之一中,我需要将从10000个项目中选择8000个样本的组合数乘以0.8 ** 8000 组合号是一个很长的long-number,在numpy的帮助下,我得到0.8**8000的结果为5.2468172239242176864e-776
但是当我试图将这两个数字相乘时,我得到了[9] 34845 segmentation fault ipython -i 那我怎么能做这样的乘法呢?

PS:这是我的一段代码

import numpy
d2 = numpy.float128(0.8) ** 8000
d1 = 165555575235503558460892983752748337696863078099010763950122624527927836980322780662408249953188062227721112100054260160204180655980717428736444016909193193353770953722788106404786520413339850951599929567643032803416164290936680088121145665954509987077953596641237451927908536624592636591471456488142060812180933761408708169972797751139799352908109763166895772281109195968567911923343187466596002627570139321755043803267091330804414889831229832744256038117150720178689066894068507531026417815624234453195871008113238128934831837842040515600131726096039123279876153916504647241693083829553081901075278042326502699324012014817969085443550523855284341221708045253558716789811929298590803855947461554713178815399150688529048306222786951038548880400191620565711291586700534540755526276938422405001345270278335726581375322976014611332999126216550500951669985289322635729053541565465940744524663726205818866513444952048185208697438054246674199211750006230637806394882672053335493831407089830994135058867370833787098758113596190447219426121568324685764151601296948654893782399960327514764114467176417125060133454019708700782282480571935020898204763471121684913190735908414301826140125010936910161942130277906874552721346626800201093026689035996876035329180150478191582393837824731994055511844267891121846403164857127885959745644323971338513739214928092232132691519007718752719466750891748327404893783451436251805894736392433617289459646429204124129760273396235033220480921175386059331059354409267348067375581516003852060360378571075522650956157791058846993826792047806030332676423336065499519953076910418838626376480202828151673161942289092221049283902410699951912366163469099917310239336454637062482599733606299329923589714875696509548029668358723465427602758225427644633549944802010973352599970041918971524450218727345622721744933664742499521140235707102217164259438766026322532351208348119475549696983427008567651685921355966036780080415723688044325099562693124488758728102729947753752228785786200998322978801432511608341549234067324280214361346940194251357867820535466891356019219904248859277399657389914429390105240751239760865282709465029549690591863591028864648910033430400L
print d1 * d2

4 个答案:

答案 0 :(得分:6)

当将极大数字乘以一个非常小的数字时,使用浮点数会导致巨大的不准确性。在您的情况下,数字的大小导致溢出错误,因此您遇到的问题不仅仅是不准确!

每当你发现自己处于这种情况时,首先检查是否可以保留在整数域,并首先“按下”这些数字会很有用。在你的情况下,它是可能的,我将在下面解释如何。

乘法的一个操作数,即极大数,是来自10000个项目的8000个样本。使用封闭形式公式计算组合数,其中样本大小n为10000,子集大小r为8000.此处的Exclam(!)是因子,您可以在{{1在python中。

math.factorial

另一个操作数C(n,r) = n! / r! (n - r)! 是极少数,按指数法则等于:

0.8 ** 8000

因此,当我们将这两个数字相乘时,我们想要的答案是:

8**8000 / 10**8000

我们将这个数字称为 10000! * 8**8000 -------------------------- 8000! * 2000! * 10**8000 ,然后取两边的对数。在对数域中工作会将乘法转换为加法,并将除法转换为减法,从而使事情更易于管理。

x

现在这些数字的大小可以在python中使用。

您会发现from math import log, factorial numerator = log(factorial(10000)) + 8000*log(8) denominator = log(factorial(8000)) + log(factorial(2000)) + 8000*log(10) log_x = numerator - denominator 等于大约3214.您现在只需要观察log_x即可找到答案。它是一个非常大但有限的数字。

答案 1 :(得分:3)

任意精度整数并不是解决这个问题的方法,因为你通过调用log来破坏你所拥有的任何精度,所以我只是让scipy.special.gammaln说话为自己(但请参阅下面的编辑):

from math import log, factorial
from scipy.special import gammaln

def comp_integral(n, r, p, q):
    numerator = log(factorial(n)) + r*log(8)
    denominator = log(factorial(r)) + log(factorial(n-r)) + r*log(q)
    return numerator - denominator

def comp_gamma(n, r, p, q):
    comb = gammaln(n+1) - gammaln(n-r+1) - gammaln(r+1)
    expon = r*(log(p) - log(q))
    return comb+expon

In [220]: comp_integral(10000, 8000, 8, 10)
Out[220]: 3214.267963130871

In [221]: comp_gamma(10000, 8000, 8, 10)
Out[221]: 3214.2679631308811

In [222]: %timeit comp_integral(10000, 8000, 8, 10)
10 loops, best of 3: 80.3 ms per loop

In [223]: %timeit comp_gamma(10000, 8000, 8, 10)
100000 loops, best of 3: 11.4 µs per loop

请注意,输出最多相同,最多14位,但gammaln版本快了近8000倍。如果您要做很​​多事情,这将会很重要。

编辑:gammaln做的是计算伽玛函数的自然对数。伽玛函数可以被认为是因子的推广,factorial(n) == gamma(n+1)。所以comb(n,r) == gamma(n+1)/(gamma(n-r+1)*gamma(r+1))。然后记录日志将其转换为上面的表格。

Gamma也有fractional inputs and for negative numbers的值。尽管如此,这并不重要。

答案 2 :(得分:2)

我维护gmpy2库,它可以非常轻松地完成此任务。

>>> import gmpy2
>>> gmpy2.comb(10000,8000) * gmpy2.mpfr('0.8')**8000
mpfr('8.6863984366232171e+1395')

答案 3 :(得分:1)

根据wim的优秀答案,您还可以将此数字存储为Fraction,方法是制作素数因子列表,执行取消操作并将所有内容相乘。

我已经为这个问题包含了一个相当天真的实现。它会在不到一分钟的时间内返回一个分数,但是如果你实现稍微更聪明的分解,你肯定可以让它更快。

from collections import Counter
from fractions import Fraction
import gmpy2 as gmpy

def get_factors(n):
    factors = Counter()
    factor = 1
    while n != 1:
        factor = int(gmpy.next_prime(factor))
        while not n % factor:
            n //= factor
            factors[factor] += 1
    return factors

factors = Counter()

# multiply by 10000!
for i in range(10000):
  factors += get_factors(i+1)

# multiply by 8^8000
factors[2] += 3*8000

#divide by 2000!
for i in range(2000):
  factors -= get_factors(i+1)

#divide by 8000!
for i in range(8000):
  factors -= get_factors(i+1)

# divide by 10^8000
factors[2] -= 8000
factors[5] -= 8000

# build Fraction
numer = 1
denom = 1
for f,c in factors.items():
    if c>0:
        numer *= f**c
    elif c<0:
        denom *= f**-c

frac = Fraction(numer, denom)

看起来像是在8.686 * 10 ^ 1395附近