【问题标题】:Python calculate large number wrongPython计算大数错误
【发布时间】:2015-06-26 22:55:16
【问题描述】:

最近我尝试使用 Python 计算加泰罗尼亚数! 我尝试了 2 种方法来计算加泰罗尼亚语数:

dp = np.zeros(160)
dp[0] = 1
for i in range(1, 100):
    for j in range(i):
        dp[i] += dp[j] * dp[i - j - 1]

n = int(raw_input())
catalan_n = int(math.factorial(n * 2) / math.factorial(n) / math.factorial(n + 1))

根据wiki,这些应该会产生相同的答案,但在我的计算机中,当 n 等于或大于 31 时,它们会给出不同的结果。

例如,给定n = 31,第一个实现yield

14544636039226908

当第二个

14544636039226909

n越大,差异越大。

那么,是什么原因呢?以及如何处理以使两种实现都给出相同(和正确)的结果?

【问题讨论】:

  • 哪个是正确答案?第一还是第二?
  • math.factorial 转换为浮点数和从浮点数转换,因此只要结果略小于整数(这是允许的!),就会引入一个小错误。这些加起来。
  • 第二个是正确的
  • 我强烈推荐阅读有关浮点数如何在内部表示的内容,它会很有趣也很有帮助。
  • @RobFoley OP 说阶乘版本给出了正确答案

标签: python python-2.7


【解决方案1】:

避免使用numpy,避免使用浮点数,只需让 Python 使用其原生整数即可:

dp = [0] * 160
dp[0] = 1
for i in range(1, 100):
    for j in range(i):
        dp[i] += dp[j] * dp[i - j - 1]

您获得了所需的结果:

>>> dp[31]
14544636039226909

【讨论】:

  • 与将 numpy 数组更改为具有整数 (int64) 类型的效果相同:dp = np.zeros(32, dtype=np.int64)(还必须减小 i 的范围以避免在较大的 n 值处溢出)
  • @DanielRenshaw 请注意,您的示例中的 64 位整数与 Python 的无限整数之间存在差异。 Python 将 1 << 100 计算为 1267650600228229401496703205376,这是一个无法用 64 位整数表示的值。
  • 与使用math.factorial的方法相比,这种方法非常慢。看我的回答。
  • @mescalinum 我确认您的解决方案要快得多。如果将第二个除法更改为乘法,它会更快:math.factorial(n * 2) // (math.factorial(n) * math.factorial(n + 1))。如果n 的阶乘不计算两次,它可以更快。无论如何,我的主要目的是表明 numpy 对于这种类型的计算完全没用。
【解决方案2】:

使用整数除法(/ 在 Python 2.x 中;// 在 Python 3.x 中;参见PEP-0238)。

如果你使用整数运算,Python 可以处理任意长度的整数。

我在 Python 3.4 中对此进行了测试:

>>> n = 31
>>> math.factorial(n * 2) // math.factorial(n) // math.factorial(n + 1)
14544636039226909

更多:

>>> n = 300
>>> math.factorial(n * 2) // math.factorial(n) // math.factorial(n + 1)
448863594671741755862042783981742625904431712455792292112842929523169934910317996551330498997589600726489482164006103817421596314821101
633539230654646302151568026806610883615856

真的可以达到任何长度:

>>> n = 3000
>>> math.factorial(n * 2) // math.factorial(n) // math.factorial(n + 1)
519462652919542881721365123011179975310102937604940266719385892606880110765316718891395071497514229126429925976055679251223128074749037
835401036449153787085998615080079472024510673995437465556202913988662201476481724554419588352460788248600870845757882846138810676725538
563107883030181266599172195406194674262178494218158106628185084640318133660880669410879631422165901582338980573378926964500556169385404
736100270128669761789892432503454091737948987203916800528049625631943853069946630768308689117691085645832918187925556506072761147675438
429882843604702193420613753732662694259398583327509305925877958076192508779774600671550059625449220766972323426048569573870742646138682
330665271970741737026351041002094725570021658043868050133870464978010862336227347394228402203592519509440711956260056901367528427111161
296369965071015622062369906953928825160542499316029260848901981705520546040735573456838161278143205046287274001985209051501791057064860
777924614712880895844889661062906810651227996795699200705689167041491295132678905362506739442596941049468768934515387686685216725429630
569388433843181310525905915079353425197760576036382793301451923253554632457764696533239230792374371551049829770586784317601794822668699
762524880276131689250405042237665587324829345738473826128110671929192283799781962486065016982222602138402014572024398921586637930463872
133232259555872008143437104541075975585105539708870387267774173630656199269799668692949281254988538412342931876350743005256155083395855
293674222742887729441736406441460871100319788599494948199980318713167545334283812660431840713561226653525108082181718879207846399491603
046897066186692086000900551598963656721594748873629207464689206076706897152859647808013130407215834207952366890322422542440601278699142
2249907274578524259056058561900439043252745600

使用math.factorial 的实现也比迭代版本快几个数量级:

p1='''
dp = [1] + [0] * n;
for i in range(1,1+n):
    for j in range(i):
        dp[i] += dp[j] * dp[i - j - 1]
'''
p2='''
math.factorial(n * 2) // math.factorial(n) // math.factorial(n + 1)
'''

# benchmarks:

import timeit

>>> timeit.timeit(p1, 'n=300', number=1000)
14.639895505999448

>>> timeit.timeit(p2, 'import math; n=300', number=1000)
0.06054379299166612

>>> timeit.timeit(p1, 'n=3000', number=10)
207.88161920005223

>>> timeit.timeit(p2, 'import math; n=3000', number=10)
0.042887639498803765

【讨论】:

    【解决方案3】:

    原因是numpy.zeros 使用float 作为默认元素数据类型。您没有展示如何在第一个版本中检索结果数字,但我假设您将其转换为 int,否则,您会看到结果为 1.45446360392e+16 或类似的东西:

    def your_version(n):
        dp = np.zeros(n + 1)
        dp[0] = 1
        for i in range(1, n + 1):
            for j in range(i):
                dp[i] += dp[j] * dp[i - j - 1]
        return int(dp[n])
    

    如果您指定要在 Numpy 中使用整数,则会删除舍入错误:使用 np.zeros(n + 1, dtype=np.uint64),结果是正确的。

    顺便说一句,@dlask 建议您避免使用 Numpy 是正确的。您可以找到相同公式的记忆版本on Rosetta code,它们都比 Numpy 版本更快:

    # The recurrent formula, memoized
    In [9]: %timeit catalan.catalan(31)
    The slowest run took 7.65 times longer than the fastest. This could mean that an intermediate result is being cached
    1000000 loops, best of 3: 249 ns per loop
    
    # The factorial formula, memoized
    In [10]: %timeit catalan.cat_direct(31)
    The slowest run took 10.21 times longer than the fastest. This could mean that an intermediate result is being cached
    1000000 loops, best of 3: 1.19 µs per loop
    
    # The recurrent formula with Numpy
    In [11]: %timeit catalan.your_version(31)
    1000 loops, best of 3: 259 µs per loop
    
    # The factorial version without memoization
    In [12]: %timeit catalan.your_other_version(31)
    100000 loops, best of 3: 6.98 µs per loop
    

    【讨论】:

      【解决方案4】:

      我很确定这只是一个除法问题,就像 5/2 会给你 2 一样。 将其更改为浮点数也不会修复它,因为浮点数只能保存几个(可能是 10 个?我不记得了)数字。

      您可以尝试先计算整个顶部,然后计算整个底部,然后将它们相除。通过这样做,您还可以检查 % 的问题,我不记得加泰罗尼亚数字的确切工作原理,但它应该为您解决它。 我希望这会有所帮助。

      【讨论】:

      • 请看cmets。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-12
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多