长亭百川云 - 文章详情

两个 Python 整数之间是如何进行大小比较的?过程并不像我们想的那样简单

古明地觉的编程教室

44

2024-07-13

楔子

通过考察整数的底层实现,我们明白了它能够保证不溢出的缘由。整数的值在底层是由 C 数组来维护的,通过组合多个 digit(uint32_t)来实现大整数的存储。这么做的好处就是 Python 整数没有大小限制了,因此不会溢出,而不会溢出是因为数组没有长度限制,所以只要你的内存足够,就可以存任意大的数。

Python 表示:存不下?会溢出?这都不是事儿,直接继续往数组里面塞 digit 就 ok 了。另外数组存的是绝对值,符号是通过 lv_tag 体现的。

不过说实话,用数组实现大整数的做法非常普遍,并没有什么神秘的,就是将多个整数组合起来,模拟具有更高位的大整数。但这种实现方式的难点在于大整数的数学运算,它们才是重点,实现的时候比较考验编程技巧以及思维逻辑。

因此 Python 的整数比浮点数要复杂的多,浮点数在底层直接用 C 的 double 来维护具体的值,因此运算的话,比如相加,直接转成 C 的 double 做加法即可。但整数就不行了,在底层不能简单地将数组相加。

下面来看看具体实现。

整数的大小比较

先来看看整数的大小比较,由于整数具体的值是通过数组维护的,显然数组越长,整数的绝对值就越大,这是毫无疑问的。至于整数的正负,则由 lv_tag 来体现。

  • 如果整数大于 0,那么 lv_tag 等于 ob_digit 长度 << 3;

  • 如果整数小于 0,那么 lv_tag 等于 (ob_digit 长度 << 3) | 2;

  • 如果整数等于 0,那么 lv_tag 等于 ob_digit 长度,显然此时两者都是 1;

所以,如果整数的 lv_tag 和 3 按位与之后结果为 0,那么是正数;如果和 3 按位与之后结果为 2,那么是负数;如果和 3 按位与之后结果为 1,那么等于 0。

因此可以得出一个结论:当两个整数在比大小时,可以先比较各自的 lv_tag。如果 lv_tag 不一样,那么可以直接比较出整数的大小。

但如果两个整数的 lv_tag 一样,那么就从数组 ob_digit 的尾部元素开始,不断向前进行比较。只要两个整数的 ob_digit 中有一个对应元素不相同,那么就可以比较出大小。

之所以从数组的尾部开始,是因为数组元素的索引越大,那么充当的位数就越高,而在比较的时候显然要从高位开始比。

# ob_digit = [892311, 32, 3]  
a1 = 3458764548181171607  
# ob_digit = [2296, 31, 3]  
a2 = 3458764547106539768

我们以 a1 和 a2 为例,显然 a1 大于 a2,那么在底层,它们是如何比较的呢?

如果 lv_tag 不相等,那么可以直接比较出大小。但这里两个整数的 lv_tag 是相等的,所以需要比较 ob_digit,并且是从后往前比。具体做法就是让索引从 ob_digit 长度减 1 开始,依次往前遍历。

  • 因为 a->long_value.ob_digit[2] 等于 b->long_value.ob_digit[2],此时无法比较出大小,因此索引减一,比较上一个元素。

  • 因为 a->long_value.ob_digit[1] 大于 b->long_value.ob_digit[1],所以成功比较出大小,可以得出 |a1| 大于 |a2|。

当然啦,由于数组反映的是绝对值的大小,所以还需要判断符号。如果是正数,那么和绝对值相同;但如果是负数,那么绝对值越大,对应的整数反而越小,因此比较之后的结果还要再乘上 -1。

from ctypes import *  
  
class PyLongObject(Structure):  
    _fields_ = [  
        ("ob_refcnt", c_ssize_t),  
        ("ob_type", c_void_p),  
        ("lv_tag", c_ssize_t),  
        ("ob_digit", c_uint32 * 3)  
    ]  
  
a1 = 3458764548181171607  
a2 = 3458764547106539768  
long_obj1 = PyLongObject.from_address(id(a1))  
long_obj2 = PyLongObject.from_address(id(a2))  
print(list(long_obj1.ob_digit))  # [892311, 32, 3]  
print(list(long_obj2.ob_digit))  # [2296, 31, 3]  

以上就是整数的大小比较逻辑,下面再看一下具体的源码实现。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2