0x00 上节我们分析了python实现大整数的方式(切分数值的绝对值存放到数组),如此一来对于大整数的运算会相应的变得复杂,本节探讨的就是python大整数运算的处理。
更多大整数实现细节,读者可以查看上节文章 -> Click me 。
数值运算 先前讨论python对象的相关内容,知道对象的行为由对象的类型决定的。因此,我们也将从整数类型对象中考察这个问题,整数类型对象
对应PyLong_Type
,定义位置/Objects/longobject.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0 ) "int" , offsetof(PyLongObject, ob_digit), sizeof (digit), long_dealloc, 0 , 0 , 0 , 0 , long_to_decimal_string, &long_as_number, 0 , 0 , (hashfunc)long_hash, 0 , long_to_decimal_string, PyObject_GenericGetAttr, 0 , 0 , Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LONG_SUBCLASS, long_doc, 0 , 0 , long_richcompare, 0 , 0 , 0 , long_methods, 0 , long_getset, 0 , 0 , 0 , 0 , 0 , 0 , 0 , long_new, PyObject_Del, };
可以看到数值型操作的PyLong_Type.tp_as_number
字段不为空,说明整数对象支持数值型操作。
而PyLong_Type.tp_as_sequence
字段以及PyLong_Type.tp_as_mapping
字段为空,说明整数对象不支持这两个类型的操作。
跟进数值型操作PyLong_Type.tp_as_number
字段的long_as_number
,同文件下发现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static PyNumberMethods long_as_number = { (binaryfunc)long_add, (binaryfunc)long_sub, (binaryfunc)long_mul, long_mod, long_divmod, long_pow, (unaryfunc)long_neg, (unaryfunc)long_long, (unaryfunc)long_abs, (inquiry)long_bool, (unaryfunc)long_invert, long_lshift, (binaryfunc)long_rshift, long_and, long_xor, long_or, long_long, 0 , long_float, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , long_div, long_true_divide, 0 , 0 , long_long, };
不为空的字段说明整数对象提供了相应的操作函数。例如,nb_add
,nb_subtract
..等,现在我们可以结合前面的指示画出:
数值加法–nb_add 整数对象的数值加法(nb_add
)特化到整数对象下是long_add
,代码位置/Objects/longobject.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static PyObject *long_add (PyLongObject *a, PyLongObject *b) { PyLongObject *z; CHECK_BINOP(a, b); if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1 ) { return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b)); } if (Py_SIZE(a) < 0 ) { if (Py_SIZE(b) < 0 ) { z = x_add(a, b); if (z != NULL ) { assert(Py_REFCNT(z) == 1 ); Py_SIZE(z) = -(Py_SIZE(z)); } } else z = x_sub(b, a); } else { if (Py_SIZE(b) < 0 ) z = x_sub(a, b); else z = x_add(a, b); } return (PyObject *)z; }
第六行做了操作数的检查(大概是long_add
只对整数对象进行加法,其他类型对象不适用)
1 2 3 4 5 #define CHECK_BINOP(v,w) \ do { \ if (!PyLong_Check(v) || !PyLong_Check(w)) \ Py_RETURN_NOTIMPLEMENTED; \ } while(0)
上述代码中频繁出现的Py_Size
是个宏定义,位置在同文件下:
#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
前面分析过ob_size
字段存放的是用于记录digit
数组中元素的个数
MEDIUN_VALUE
的定义也在同文件下:
1 2 3 4 5 #define MEDIUM_VALUE(x) (assert(-1 <= Py_SIZE(x) && Py_SIZE(x) <= 1), \ Py_SIZE(x) < 0 ? -(sdigit)(x)-> ob_digit[0] : \ (Py_SIZE(x) == 0 ? (sdigit)0 : \ (sdigit)(x)->ob_digit[0]))
1 2 3 4 #if PYLONG_BITS_IN_DIGIT == 30 typedef int32_t sdigit; #elif PYLONG_BITS_IN_DIGIT == 15 typedef short sdigit;
可以看到,sdigit
只是C语言下的int32_t
和 short
的别名,具体是哪个得参考PYLONG_BITS_IN_DIGIT
的内容
到这里,就清楚MEDIUM_VALUE
完成的是当指示digit
数组长度的ob_size
为1、0、-1时,此时仅仅使用C语言就足够运算了,因此转为C语言整数进行运算,运算结果使用PyLong_FromLong
转为python对象
第11-22行,当a、b皆为负数(ob_size
的符号表示正负。)时,使用x_add
,进行运算。
第11行+第23-25行,当a为负数,b为正数,使用x_sub
进行运算
第11行+第26-28行,当a为正数,b为负数,使用x_sub
进行运算
第11行+第29-31行,当a为正数,b为正数,使用x_add
进行运算
可以看到,根据a、b符号进行了不同的操作,但最终回到两个关键的函数x_add
& x_sub
,那么接着探讨这两个函数的处理过程
x_add x_add
定义位置在同文件下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static PyLongObject *x_add (PyLongObject *a, PyLongObject *b) { Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); PyLongObject *z; Py_ssize_t i; digit carry = 0 ; if (size_a < size_b) { { PyLongObject *temp = a; a = b; b = temp; } { Py_ssize_t size_temp = size_a; size_a = size_b; size_b = size_temp; } } z = _PyLong_New(size_a+1 ); if (z == NULL ) return NULL ; for (i = 0 ; i < size_b; ++i) { carry += a->ob_digit[i] + b->ob_digit[i]; z->ob_digit[i] = carry & PyLong_MASK; carry >>= PyLong_SHIFT; } for (; i < size_a; ++i) { carry += a->ob_digit[i]; z->ob_digit[i] = carry & PyLong_MASK; carry >>= PyLong_SHIFT; } z->ob_digit[i] = carry; return long_normalize(z); }
第1行,注释中表明该函数完成的是两个整数对象绝对值相加
第5行,比较a、b整数对象存放数值的digit
数组的长度(忽略正负),分别存放到size_a
size_b
中
第11-16行,对比size_a
size_b
,保证a为二者长度较大的一个,否则交换a、b对象
第17行,_PyLong_New
创建新的整数对象z,digit
数组长度取a+1(用于潜在进位保留)
第21-24行,ob_digit
每一个元素表示的范围2 ** 30
当超过则会溢出,因此carry
保存的进位信息
这里需要解决22行中z->ob_digit[i] = carry & PyLong_MASK;
,跟踪PyLong_MASK
在同文件下找到定义
1 2 3 4 5 #define PyLong_MASK ((digit)(PyLong_BASE - 1)) #define PyLong_BASE ((digit)1 << PyLong_SHIFT) #define PyLong_SHIFT 15
大致完成:第21行carry += a->ob_digit[i] + b->ob_digit[i]
并做了相同的移位操作,第22行位置z->ob_digit[i]
中保留非进位部分,第23行,将进位carry
右移参与下一个digit数组的运算
注:PyLong_MASK
将1进行左移PyLong_SHIFT
位(15)得到 1000 0000 0000 0000
,将这个值-1,得到PyLong_MASK = 0111 1111 1111 1111
。
第25-29行,这块处理的是a中比b高出的部分。,行为同第21-24行一致。
第30行,将z对象digit
数组最高位保存进位。
第31行,调用long_normalize
对z对象进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 static PyLongObject *long_normalize (PyLongObject *v) { Py_ssize_t j = Py_ABS(Py_SIZE(v)); Py_ssize_t i = j; while (i > 0 && v->ob_digit[i-1 ] == 0 ) --i; if (i != j) Py_SIZE(v) = (Py_SIZE(v) < 0 ) ? -(i) : i; return v; }
取出对象高位为0 的部分,并完成ob_size
的设置
实例演示过程
x_sub 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 static PyLongObject *x_sub (PyLongObject *a, PyLongObject *b) { Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); PyLongObject *z; Py_ssize_t i; int sign = 1 ; digit borrow = 0 ; if (size_a < size_b) { sign = -1 ; { PyLongObject *temp = a; a = b; b = temp; } { Py_ssize_t size_temp = size_a; size_a = size_b; size_b = size_temp; } } else if (size_a == size_b) { i = size_a; while (--i >= 0 && a->ob_digit[i] == b->ob_digit[i]) ; if (i < 0 ) return (PyLongObject *)PyLong_FromLong(0 ); if (a->ob_digit[i] < b->ob_digit[i]) { sign = -1 ; { PyLongObject *temp = a; a = b; b = temp; } } size_a = size_b = i+1 ; } z = _PyLong_New(size_a); if (z == NULL ) return NULL ; for (i = 0 ; i < size_b; ++i) { borrow = a->ob_digit[i] - b->ob_digit[i] - borrow; z->ob_digit[i] = borrow & PyLong_MASK; borrow >>= PyLong_SHIFT; borrow &= 1 ; } for (; i < size_a; ++i) { borrow = a->ob_digit[i] - borrow; z->ob_digit[i] = borrow & PyLong_MASK; borrow >>= PyLong_SHIFT; borrow &= 1 ; } assert(borrow == 0 ); if (sign < 0 ) { Py_SIZE(z) = -Py_SIZE(z); } return long_normalize(z); }
经过x_add
,相信读者已经可以开始自己尝试分析x_sub
了。