python dict.values() 的 sum() 结果不正确?
亲爱的Stack交流者们,
我在用Python计算字典中数值的总和时遇到了奇怪的结果。如果我的字典超过了某个大小,使用这个函数:sum(dict.values())
似乎会给出错误的结果。结果突然变成负数,完全没有理由。我是在Windows 7上使用Python 2.7。(提前为我的编码风格道歉)。
请注意,我在处理https://projecteuler.net/problem=72时遇到了这种情况,但我并不是在寻求答案的帮助,只是对这个内置函数的表现感到困惑。(我也为在公共论坛上发布部分解决方案而道歉,如果你不想看到任何提示,请现在就移开视线)。
程序的目标在上面的项目Euler链接中有解释,但我会尝试简要说明我的代码:
第一个函数使用埃拉托斯特尼筛法生成素数列表,并使用修改过的筛法在指定范围内生成一个字典,格式为{合成数:[素因子列表]}
。
第二个函数试图计算可以生成的形如n/d的分数的数量,其中n < d且d <= 1000000。问题说明我只应该计算约简的真分数,所以这个函数的大部分内容都在处理可约分数。我采用的策略是循环遍历每个分子,从1到d-1,并丢弃不合适的分母。对于素数来说,这很简单,而对于非素数,我接近一个可行的解决方案,但仍然会多次丢弃一些值。对于这篇帖子,重要的细节是我统计计数的方式:
最开始我使用了一个简单的计数器(将计数初始化为0,然后根据需要增加),但后来决定尝试使用字典。令我惊讶的是,这两种方法给出的结果不同,但只有当上限(d)超过某个大小时。我深入探究,成功找到了计数结果分歧的确切时刻。代码行if 88000 < i < 88055:
在底部标识了字典值的总和开始与简单计数不同的点。对于i = 88032之前的值,结果是相同的,但当i = 88033时,结果却大相径庭:
from collections import defaultdict
def primeset(limit):
pr = [0]*(limit+1)
for i in range(2,limit+1):
j = i
i += j
while i <= limit:
pr[i] = 1
i += j
primes = [k for k in range(2,limit+1) if pr[k] == 0]
composites = defaultdict(list)
for p in primes:
q = p
p += q
while p <= limit:
composites[p].append(q)
p += q
return primes, composites
def method2(limit):
primes, composites = primeset(limit)
prf = {}
count = 0
count += limit-1
count += (limit-2)/2
prf[1] = limit-1
prf[2] = (limit-2)/2
for i in primes:
if i != 2:
tally = limit-i-(limit/i)+1
count += tally
prf[i] = tally
for i in composites:
tally = limit-i
for item in composites[i]:
tally -= (limit/item-i/item)
count += tally
prf[i] = tally
if 88000 < i < 88055:
print i, count, tally, sum(prf.values())
return count, prf
result, index = method2(88547)
print result,sum(index.values())
我想我可能做了什么愚蠢的事情,但我觉得有必要把这个问题提出来,以防真的有什么不对劲。
祝好,
1 个回答
你遇到了整数溢出的问题,而在Python中,这种情况本来是不应该发生的。你使用的是32位的机器,所以最大的正常整数是(2^31 - 1)。一旦你的计算超过这个值,Python应该会自动切换到使用长整型(long)进行计算,这种类型的数字大小没有限制。我只用过64位的机器,但情况是一样的,只不过最大整数变成了(2^63 - 1)。你可以通过命令行看到长整型,因为在数字后面会有一个L。下面是我在命令行中的一个例子:
>>> 2**62 - 1 + 2**62 # This is max int
9223372036854775807
>>> 2**63 # This is a long
9223372036854775808L
>>> num = 2**62 - 1 + 2**62
>>> num
9223372036854775807
>>> num+1
9223372036854775808L
>>> d = {1:2**62,2:-1,3:2**62}
>>> sum(d.values())
9223372036854775807
>>> d = {1:2**62,2:-1,3:2**62,4:1}
>>> sum(d.values())
9223372036854775808L
在我的情况下,使用的是64位机器上的Python 2.7,所有的计算都按预期工作。
现在我在Spyder中运行相同的代码,却得到了错误的结果:
>>> d = {1:2**62,2:-1,3:2**62,4:1}
>>> sum(d.values())
-9223372036854775808
当我进行普通的加法时,它能正确处理,但从字典中得到的这个和却是错误的。这并不是字典特有的问题,而是sum函数的问题。列表也是一样:
>>> list = [2**62, -1, 2**62, 1]
>>> sum(list)
-9223372036854775808
所以问题就集中在Spyder中的sum()函数上,这在32位和64位机器上都会发生。
真正的原因是Spyder会自动导入numpy。numpy有自己版本的sum函数。它的描述是“使用整数类型时,算术是模运算的,溢出时不会引发错误。”你正在使用这个版本的sum,这就是问题所在。如果你不想使用这个sum,可以在文件的顶部加上以下代码:
from __builtin__ import sum
这样就会使用内置的sum版本,你就能得到正确的结果。
为了弄清楚sum不是来自我想的地方,我可以使用以下代码:
>>> import inspect
>>> inspect.getmodule(sum)
<module 'numpy.core.fromnumeric' from '/usr/lib/python2.7/dist-packages/nump/core/fromnumeric.pyc'>