Python如何处理比64位无符号整数限制更大的数字?

4

根据这个问题(How big can a 64bit signed integer be?),我了解到在一个64位机器上可使用的最大数值为2^64-1,即92,233,720,368,547,758,070。这意味着,即使我加上1,它也应该返回inf。但事实并非如此。下面是我的观察:

>>> max = sys.maxsize
>>> format(max, ',')
'9,223,372,036,854,775,807'
>>> a = max * 10
>>> format(a, ',')
'92,233,720,368,547,758,070'
>>> a / max
10.0

即使由于某种原因 92,233,720,368,547,758,070 不是 Python 中最大的数字,那么 sys.maxsize 有什么用呢?
其次,一个64位数字不应该占用64位内存空间吗?为什么 maxa 都只占用了 36字节
>>> sys.getsizeof(max)
36
>>> sys.getsizeof(a)
36

有人能描述一下这两个概念的混淆吗?


2
Python中的整数可以任意长(并且它们是Python对象[https://docs.python.org/3/c-api/structures.html#c.PyObject],具有引用计数等 - 因此它们占用的空间比仅64位要多)... sys.maxsize仍然相关 - 您只能将列表/元组寻址到该大小(嗯 - 在达到这个限制之前,您将耗尽内存...)。 - hiro protagonist
那么为什么这个问题(https://dev59.com/82025IYBdhLWcg3wcli-)说最大的数字是“92,233,720,368,547,758,070”?`sys.maxsize`还有用吗?请举个例子。 - Fahim
Python的整数不是本地64位有符号数字。因此,本地64位数字的大小限制是无关紧要的。 - MisterMiyagi
这是“Py_ssize_t”的限制,它不是Python的“int”。 - MisterMiyagi
2
顺便提一下,Python 2 中有 sys.maxintsys.maxsize。现在后者与整数没有太多关系,它是指平台上最大的 Py_ssize_t - 在日常 Python 编程中不再真正相关,除非你编写 C 扩展程序。 - wim
显示剩余6条评论
1个回答

6

整数作为数字数组

Python 3(CPython)的整数不是本机整数。逻辑上,每个整数由其符号和基于1073741824(30位)或32768(15位)[*]的绝对数字组成 - 后者是无符号整数的可变大小数组。为了存储更大的数字,数组中添加了一个额外的“数字”。

>>> sys.getsizeof(0)          # largest  0-digit number
24
>>> sys.getsizeof(1)          # smallest 1-digit number
28
>>> sys.getsizeof(2**30 - 1)  # largest  1-digit number
28
>>> sys.getsizeof(2**30)      # smallest 2-digit number
32
>>> sys.getsizeof(2**60 - 1)  # largest  2-digit number
32

粗略地说,这与写十进制数字时添加数字的机制相同——使用1个数字足以表示9,2个数字足以表示99等。同样,只要计算机有“添加数字”的内存,就可以定义更大尺寸的Python整数。

[*] 数字是30位/15位而不是32位/16位,因为这更适合某些算法。例如,long_pow()需要可被5整除的大小。

整数对象头

实际上,整数也是对象——意味着它们包含元数据,如类型和引用计数,这也占用空间。在CPython中,一个int包括

  • Py_ssize_t 的引用计数器
  • PyTypeObject* 类型的指针
  • Py_ssize_t 的数字数量
  • digit[] 的可变数字数组

其中前三个是每个可变大小对象的结构。符号编码在数字数量中。

在 64 位机器上,Py_ssize_tPyTypeObject* 都是 8 字节大小,因此 "0-数字整数" 0 的大小为 3*8 字节或 24 字节。

>>> sys.getsizeof(0)          # largest  0-digit number
24

那么什么是sys.maxsize

sys.maxsize的含义并不是最大的整数大小,而是最大的容器大小:

>>> len(range(sys.maxsize))    # this is fine
9223372036854775807
>>> len(range(sys.maxsize+1))  # this is one too much
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: range() result has too many items

这是因为sys.maxsize表达了Py_ssize_t的最大值,而Py_ssize_t是CPython运行时用于表示和寻址内存的类型。虽然这似乎是一个任意限制,但实际上比计算机可寻址的内存要多得多


1
写上“自CPython3.9起”让人感觉好像最近有什么变化,但据我所知已经有好几年没有变化了(甚至可能自Python 3.0以来都没有)。 - wim
@wim 感谢您的反馈,但我没有更好的措辞。我不想说“自 CPython3.0 起”,因为对我来说这意味着它就像“当时”一样。我应该放弃版本引用吗? - MisterMiyagi
1
是的,我想是这样的。只是“在CPython中,int…” - wim
@Fahim 如果你习惯于使用C或C++,这将更有意义。它是一种整数类型,足以容纳内存中任何可能对象的大小。对于64位系统,它将是64位。 - Mark Ransom
Py_ssize_t 是在 CPython 中用于存储容器大小和元素位置的类型。它源自 POSIX|C|C++ 的 ssize_t。虽然有符号类型限制了容器的最大大小(在 64 位机器上为 8EiB,而不是 16 EiB),但它允许安全地处理和存储负索引。负索引在 Python 中经常使用,因此正确支持它们非常重要。 - MisterMiyagi
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接