当前位置: 首页 > 科技观察

Python如何实现不溢出的整数加法?

时间:2023-03-13 01:31:19 科技观察

1。如何表示一个整数要理解这一点,您需要查看Python[1]的源代码。Python中整数的底层结构是PyLongObject,它位于longobject.h[2]中。一步步展开如下://longobject.htypedefstruct_longobjectPyLongObject;/*Revealedinlongintrepr.h*///longintrepr.hstruct_longobject{PyObject_VAR_HEADdigitob_digit[1];};//一起可以看成typedefstruct{PyObject_VAR_HEADdigitob_digit[1];}PyLong定义PyObject_VAR_HEAD展开:typedefstruct{PyObject_HEADintob_size;digitob_digit[1];}PyLongObject;然后展开宏定义PyObject_HEAD,结构体中的变量我已经注释了:typedefstruct{intob_refcnt;//引用计数struct_typeobject*ob_type;//变量类型intob_size;//用来表示变量中包含多少个元素-lengthobjectdigitob_digit[1];//数字类型数组,长度为1}PyLongObject;这里的ob_size用来表示变长对象元素中容纳了多少个元素,即ob_digit数组的长度,这个ob_digit数组只能用来维护具体的值。这里已经很明显了,Python将大整数切开,存储了ob_digit。这个数组的长度是可变的。数据越大,数组越长。只要内存足够,你可以存储任意数量的数字。那么下面的重点就是这个ob_digit数组。我们来看看Python中一个整数对应的值,比如256,是如何放在这个数组中的。但首先我们需要看看这个数字是什么类型,这在longintrepr.h中也有定义。#ifPYLONG_BITS_IN_DIGIT==30typedefuint32_tdigit;//...#elifPYLONG_BITS_IN_DIGIT==15typedefunsignedshortdigit;//...#endifPYLONG_BITS_IN_DIGIT是个宏,如果你的机器是64位的,那么会定义成30位的,32位的机器会是定义为15。而我们现在的机器基本都是64位的,所以PYLONG_BITS_IN_DIGIT会等于30,因为digit相当于uint32_t(unsignedint),所以是一个无符号的32位整数。所以ob_digit数组是一个无符号的32位整数数组,长度为1。当然这个数组的具体长度取决于你要存储的Python整数的大小,因为C中数组的长度并不属于类型信息,可以看成是长度n,这个n的大小取决于你整数的大小。显然,整数越大,数组越长,占用的空间也越大。为了说明ob_digit是如何存储256的,我们简化一下。这里如果ob_digit数组是一个无符号的8位整型数组,8位二进制,最多只能表示255。如果我们要表示256,那么只能再申请一个8位,可能你认为再申请一个8位来表示1,其实不是,它是用一个新的8位整数来表示模拟高位,如下:255=[255]256=[1,1]256=[1,1]的形式不是真实情况。为了大家的理解,先这样写,表示256=1+1*(2^8-1)=1+1*255=256。也就是说ob_digit表示以x为基数的数,ob_digit[0]为低位,ob_digit[1]为高位。x的具体取值取决于ob_digit的类型。这里8位是以255为基数的,刚才说的256=[1,1]的形式并不是真实情况,因为PyLongObject不仅要存储大整数,还需要参与运算。怎么操作,就是把ob_digit一点一点的加起来。既然是加法,可能会溢出,比如这里的[255,1]+[255,1]=[510,2],510超过了8位,为了简化流程,只要我们不需要填充8位,它不会溢出,也就是说如果只用7位的话,最大就是[127,...]+[127,...]=[254,...]它不会溢出。到这里,你就会明白为什么digit虽然是一个无符号的32位整数,但它只用了30位:#ifPYLONG_BITS_IN_DIGIT==30typedefuint32_tdigit;//...#elifPYLONG_BITS_IN_DIGIT==15typedefunsignedshortdigit;//...#endifsmartYou可能会问,31位能保证不溢出,为什么要牺牲两位而用30位,我也不知道答案,可能是因为64是32的两倍,30也是15的两倍,看这样起床更舒服。如何表示负数?实际上,如果number为负数,ob_size变为负数,其他不变。整数的符号在这里由ob_size决定。ob_digit实际存储的是绝对值,不管n是多少,-n和n对应的ob_digit是完全一样的,只是ob_size是相反的。所以ob_size不仅表示数组的长度,还表示对应整数的符号。因此,Python在比较两个整数的大小时,会先比较ob_size。如果ob_size不同,可以直接比较大小。综上所述,当PYLONG_BITS_IN_DIGIT==30时,整数=ob_digit[0]+ob_digit[1]*2**30+ob_digit[2]*2**60+...2.整数占用的内存大小为理解到这里,我们再来看看这个结构体:typedefstruct{intob_refcnt;//引用计数struct_typeobject*ob_type;//可变类型intob_size;//用来表示有多少个元素digitob_digit[1];//digit类型数组,长度为1}PyLongObject;整数占用多少字节取决于PyLongObject结构占用多少字节,ob_refcnt、ob_type、ob_size是整数必须的,都是8字节,加起来24字节。因此,任何整数占用的内存至少是24字节。至于占多少,要看ob_digit中元素的个数。现在你可以很容易地理解下面的结果:3.整数池另外,Python中的整数是不可变对象,运行后会创建新的对象:>>>a=300>>>id(a)140220663619152>>>a+=1>>>id(a)140220663619408>>>这样难免会有性能上的缺陷,因为程序运行时会创建和销毁对象,涉及内存申请和垃圾回收。一种常用的方法是使用对象池。高频的整数是预先创建好的,都是单例模型,需要的时候可以直接返回。小整数对象池的实现位于pycore_interp.h[3]:验证:>>>a=-6>>>b=-6>>>aisbFalse>>>a=-5>>>b=-5>>>aisbTrue>>>a=256>>>b=256>>>aisbTrue>>>a=257>>>b=257>>>aisbFalse>>>不同版本可能不一样,我这边蟒蛇3。8,区间为[-5,257)。4.整数加减法有前面的铺垫。现在让我们看看在Python中如何添加大整数。longobject.c源代码:long_add函数[4]。你可以看到long_add调用x_add或x_sub取决于ob_size是正数还是负数。现在看x_add的源码:可以看到Python大整数相加就是底层数组的相加,当然还有进位等操作:for(i=0;iob_digit[i]+b->ob_digit[i];z->ob_digit[i]=carry&PyLong_MASK;carry>>=PyLong_SHIFT;}x_sub源码:4.整数乘法Python整数乘法使用Karatsubamultiplication[5]算法进行的大数乘法运算,有兴趣的可以研究一下。最后,源代码下没有秘密。读源码会吃力,但可以学到精髓和精髓。本文逐层展开源码,带你了解Python整数对象的实现,整数内存大小的计算,整数池,整数加减法源码,相信你已经知道Python是如何实现整数加减法的,不用溢出。如果您有收获,请点赞转发。感谢您一路以来的支持与陪伴。