模拟python底层数据类型,大整数的实现!
作者:互联网
我们参考C源码的逻辑,试着用python去模拟一下大整数的实现,这里 只实现了加减法的操作。
(感谢作者的专栏,收获很大,参考 慕课网:《Python 源码深度剖析》)
(1)类: Int32()
- 这是设计出来的,用来模仿 int类型 4字节的整形数据。
- 定义了加减乘除4个基本运算
- 利用数据描述器,对数据进行限制。最大的取值只有 -2 **31 到 2 **31 -1
- 当运算结果大于 这个范围,便会抛出异常,提示你 整数溢出
- 提供一个转换函数,当结果取值大时, 转换为 大整数类型进行计算(PyLongObject())
- 这里需要注意的是, Int32 和 PyLongObject 之间不能运算。 所以并未 在 Int32 做运算是,直接内部进行转换,返回 PyLongObject, 为的就是类型混淆。 用户自己调用方法,将 int32 转换为 大整形进行计算。
(2) 类:PyLongObject()
- 定义了大整数, 并定义了 大整数 加法和 加法运算
- 定义 类方法: conver, 可直接以类调用,在 int32 转换起来数值较小时,可直接 a = PyLongObject().conver(1231232132132131231232131231231231) , 创建一个超级大的 大整数对象。 并且,Int32的convert() 转换函数,也是调用这个方法实现。
- 也定义了 conver2 方法,加 @property装饰器, 在大整数数值较大时, 可直接 像属性一样调用该方法,返回 python 类型的数值,观察它的数值。
(3)类:PyLong_Type()
- 定义了 绝对值加法和减法, 供PyLongObject() 对象实例进行运算时调用。
- 另外 类型对象提供相应的方法函数,直接定义为单例,实例对象全局唯一,节省内存的开销。
(4) Base = 2 ** 30 定义在顶部的一个 全局常量。 数组表示大整数的时候,代表每个单元最大的存储,也相当于是进制。 再做基本的加减法运算的时候, 不会超过 int32 表示的大小,方便计算,也是细节之处!
下面上代码:(注释部分为测试程序功能代码,没其他作用)
# 这里用来实现,大整数
Base = 2 ** 30
# 定义 大整数的加减操作
class PyLong_Type():
'''
tp_as_number 指向 PyNumberMethods 方法集
把类型对象设置为单例,全局只有一份
'''
def __new__(cls, *args, **kwargs):
if not hasattr(PyLong_Type, "_instance"):
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
# 直接将方法集中的方法定义到 PyLong_Type 里了
def x_add(self, a, b):
''' 绝对值加法 '''
size_a = abs(a.ob_size)
size_b = abs(b.ob_size)
carry = 0
if size_a < size_b:
a, b = b, a
size_a ,size_b = size_b, size_a
z = PyLongObject(ob_size=size_a + 1)
# 解决 有 0 参与加法的情况(C语言的下面两个循环用python来写,无法解决有0的参与运算。因为python最后的 i 不会自增 1,C语言写的,加入第一个数字是零,那么直接从第二个循环开始,但是python从第二个循环开始,j 值 是等于 i+ 1的,也就是 C 与python的区别, 第一个i++会根据情况加, python的range只能使 i循环到比循环数小 1)
if size_a == 0:
return b
elif size_b == 0:
return a
else:
pass
for i in range(size_b):
carry += a.ob_digit[i] + b.ob_digit[i]
z.ob_digit[i] += carry % Base
# 得到进位
carry = carry // Base
for j in range(i+1, size_a):
carry += a.ob_digit[j]
z.ob_digit[j] = carry % Base
carry = carry // Base
# carry 可能还有一个进位
if carry != 0:
z.ob_digit[size_a] = carry
else:
del z.ob_digit[size_a]
z.ob_size -= 1
return z
def x_sub(self, a, b):
''' 绝对值减法运算 '''
size_a = abs(a.ob_size)
size_b = abs(b.ob_size)
z = PyLongObject()
# 绝对值减法结果符号, 1表示正, -1表示负
sign = 1
# 表示 向高位的借位
borrow = 0
# 用来循环计数(没有定义这个,有0参与运算时下面逻辑,可能会出错)
i = 0
# 算法类似于 上面的绝对值加法, 确保a 的绝对值长度 大于等于b
if size_a < size_b:
sign = -1
a, b = b, a
size_a, size_b = size_b, size_a
# 当两者相同长度的时候,挨个从高位依次比较,将大的数,赋值给a
elif size_a == size_b:
for i in range(size_a - 1, -1, -1):
if a.ob_digit[i] != b.ob_digit[i]:
break
if (i-1 < 0):
return z
if (a.ob_digit[i] < b.ob_digit[i]):
sign = -1
a, b = b, a
# 此时 大于i的数相等,相减为0,只需从 i位置往后进行计算即可
size_a = size_b = i + 1
i = 0
z = PyLongObject(size_a)
for i in range(size_b):
borrow = a.ob_digit[i] - b.ob_digit[i] - borrow
z.ob_digit[i] = borrow % Base
# 三元表达式
borrow = 1 if borrow < 0 else 0
if i == 0:
i = -1
# (a 长度大于 b的情况 )当还有借位时,继续
for j in range(i + 1, size_a):
borrow = a.ob_digit[j] - borrow
z.ob_digit[j] = borrow % Base
borrow = 1 if borrow < 0 else 0
# 判断结果 正负
if sign < 0:
z.ob_size = -z.ob_size
return z
# 自定义大整数类型:
class PyLongObject():
Py_long = PyLong_Type()
def __init__(self, ob_size = 0):
"""初始化大整数
:param ob_refcnt: 引用计数
:param ob_type: 类型指针( 这里规定它所使用的方法如 + - 操作 )
:param ob_size: ob_digit 数组的长度
:param ob_digit: 用来保存大整数的绝对值(将大整数分为若干段进行保存)
"""
# PyVarobject(变长对象)
self.ob_refcnt = 1
self.ob_type = PyLong_Type
self.ob_size = ob_size
self.ob_digit = []
for i in range(self.ob_size):
self.ob_digit.append(0)
# 重写加减法运算
def __add__(self, other):
# 两个数字均不大于 2*30, 那么直接转换为 原类型(Int32), 相加的数字不会超过2 **32
if abs(self.ob_size) <= 1 and abs(other.ob_size) <= 1:
return PyLongObject.conver(self.conver2 + other.conver2)
if self.ob_size < 0:
if other.ob_size < 0:
z = self.Py_long.x_add(self, other)
z.ob_size = -z.ob_size
else:
z = self.Py_long.x_sub(other, self)
else:
if other.ob_size < 0:
z = self.Py_long.x_sub(self, other)
else:
z = self.Py_long.x_add(self, other)
return z
def __sub__(self, other):
''' 与加法得算法逻辑基本相同,都是调用 绝对值加减法来实现 '''
if abs(self.ob_size) <= 1 and abs(other.ob_size) <= 1:
return PyLongObject.conver(self.conver2 - other.conver2)
if self.ob_size < 0:
if other.ob_size < 0:
z = self.Py_long.x_sub(self, other)
else:
z = self.Py_long.x_add(self, other)
z.ob_size = -z.ob_size
else:
if other.ob_size < 0:
z = self.Py_long.x_add(self, other)
else:
z = self.Py_long.x_sub(self, other)
return z
def __mul__(self, scale):
raise("暂无乘法功能,敬请期待~")
def __truediv__(self, scale):
raise("暂无除法功能,敬请期待~")
@classmethod
def conver(self, num):
''' 实现进制的转换 '''
a = PyLongObject()
if isinstance(num, Int32):
m = num.num
z = abs(num.num)
print(m, '---',z)
else:
if num < 0:
m = num
z = abs(num)
else:
m = z = num
if z == 0:
return a
else:
while(z != 0):
a.ob_digit.append(z % Base)
print(z % Base)
a.ob_size += 1
z //= Base
if m < 0:
a.ob_size *= -1
return a
@property
def conver2(self):
'''转换回 Int32 数字'''
dec = 0
for i, num in enumerate(self.ob_digit):
dec += num * Base ** i
# 三元表达式,为值增加负号
dec = dec * -1 if self.ob_size < 0 else dec
return dec
# 因为是用Python来模拟大整数,但是 Python的数字计算已经是大整数了,所以我们来模拟一下,自建对象将Python的整形缩小到32位int
# 定义数据描述器,限制整数的大小为32位int型
class NumField():
def __init__(self, attrname):
self.attrname = attrname
def __get__(self, instance, owner):
return getattr(instance, self.attrname)
def __set__(self, instance, value):
if -2**31 <= value and value <= 2**31 -1:
setattr(instance, self.attrname, value)
else:
raise("整数溢出,超出最大长度")
# 自定义整数,大小不能超过int(32字节)
class Int32():
num = NumField("_num")
pylong = PyLongObject
def __init__(self, num):
self.num = num
# self.num_long = self.__convert(num)
def __str__(self):
return str(self.num)
# 重写加减法运算
def __add__(self, other):
return Int32(self.num + other.num)
def __sub__(self, other):
return Int32(self.num - other.num)
def __mul__(self, other):
return Int32(self.num * other.num)
def __truediv__(self, other):
return Int32(self.num / other.num)
def convert(self):
a = self
return self.pylong.conver(a)
# 测试代码逻辑正确性
# 测试 1(测试绝对值加法): 大整数表示的 数字连加 9999次 和 实际python数字运算 9999次 值转化一致,是否相等
'''
suab = Int32(1073741823)
b =
g = b.convert()
c = PyLongObject()
for i in range(9999):
print("第",i,"次")
c += g
print(g.conver2, g.ob_size, g.ob_digit)
print(c.conver2, c.ob_size, c.ob_digit)
print("+++++++++++++++")
print(1073741823*9999)
'''
# 测试2(测试绝对值减法): 利用Python 构造两个超大的数,进行大整数运算(连加得到的数字大小有限)
'''
a = -99999999999999999999999 * 1231231231231231231237843653274612123
# p = a * -3
n = PyLongObject()
v = PyLongObject()
m = PyLongObject().conver(a)
# n = PyLongObject().conver(p)
print(m.ob_digit)
print(m.ob_size)
print(n.ob_digit)
print(n.ob_size)
q = n + v
print(q.ob_digit)
print(q.ob_size)
'''
# 测试三 利用减法对大整数进行计算
'''
a = PyLongObject.conver(92341267896345634634334526)
b = PyLongObject.conver(-83671636456362623643653463634)
print('====')
print(a.conver2)
print(a.ob_digit)
print(a.ob_size)
print(a.conver2)
print('=====')
print(b.conver2)
print(b.ob_digit)
print(b.ob_size)
c = b - a
print(c.conver2)
print(c.ob_digit)
print(c.ob_size)
'''
总结
参考Cpython的底层实现, 整体代码有些过度生硬,可能原因是参考C源码,导致写的 python代码,好像有些缺失面向对象的味道。 在一个 , 源码C的 实现真的是简洁高效!! 绝对值加减的实现, 用两个循环,对所有的情况进行处理。如此简洁,流畅! 在转换为 python代码的时候,期间还是测试出了很多bug,一一解决之后, C 语言的 for 循环 i++, 运用的非常灵活,可在需要时 ++ 或者不 ++, 而python的 rang 只能让 i 最终保持在 循环的 前一个数字, 在对于 有 0参与进来的运算时,python 除了这双重循环, 我还另加了一个逻辑判断。
如此简单高效的代码,逻辑并不是非常复杂, 都是普通代码,普通语法。 但我想以我的实力~ 也很难写出这么高效简洁的代码。
标签:digit,python,数据类型,ob,borrow,carry,底层,size 来源: https://blog.csdn.net/pythonstrat/article/details/114638887