CPython如何实现超大整数

相关位置文件

  • cpython/Objects/longobject.c
  • cpython/Include/longobject.h
  • cpython/Include/longintrepr.h

加法的实现

整数是“数字方式”持久化的,这意味着加法就像我们在小学学到的一样简单,python 的源代码向我们展示了这也是它的实现方式。文件longobject.c中名为x_add的函数执行两个数字的相加。

  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;

上面的代码片段取自x_add函数,您可以看到它遍历数字并执行数字加法并计算和传播进位。

当加法的结果是负数时,事情就变得有趣了。的符号ob_size是整数的符号,这意味着,如果你有一个负数,那么它就是ob_size负数。的绝对值ob_size将决定 中的位数ob_digit。

减法的实现

与加法的实现方式类似,减法也以数字方式进行。文件longobject.c中名为x_sub的函数执行两个数字的减法。

 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; /* Keep only one sign bit */
    }
    for (; i < size_a; ++i) {
        borrow = a->ob_digit[i] - borrow;
        z->ob_digit[i] = borrow & PyLong_MASK;
        borrow >>= PyLong_SHIFT;
        borrow &= 1; /* Keep only one sign bit */
    }

上面的代码片段取自x_sub函数,您可以看到它如何迭代数字并执行减法以及计算和传播 burrow。确实非常类似于加法。

乘法的实现

再一次,实现乘法的天真方法将是我们在小学数学中学到的,但它不会很有效。Python,为了保持高效,实现了Karatsuba 算法,该算法在 O ( nˡᵒᵍ³ ) 基本步骤中将两个 n 位数字相乘。
该算法略显复杂,不在本文讨论范围内,但您可以在 longobject.c 文件中的k_mul和k_loplateral_mul

函数中找到它的实现。

内存构造

在这里插入图片描述

在 python3 之后, 就只有一个叫做 int 的类型了, python2.x 的 long 类型在 python3.x 里面就叫做 int 类型

int 在内存空间上的构造和 tuple 元素在内存空间的构造 非常相似

很明显, 只有 ob_digit 这一个位置可以用来存储真正的整数, 但是 cpython 如何按照字节来存储任意长度的整型的呢?

我们来看看

内部元素如何存储

整数 0

在这里插入图片描述

注意, 当要表示的整数的值为 0 时, ob_digit 这个数组为空, 不存储任何东西, ob_size 中的 0 就直接表示这个整数的值为 0, 这是一种特殊情况

i = 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYT192S4-1656252681290)(https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/long/0.png)]

整数 1

在这里插入图片描述

ob_digit 可以有两种不同的定义, 具体是 uint32_t 还是 unsigned short 取决于操作系统

#if PYLONG_BITS_IN_DIGIT == 30
typedef uint32_t digit;
typedef int32_t sdigit;
typedef uint64_t twodigits;
typedef int64_t stwodigits; /* signed variant of twodigits */
#define PyLong_SHIFT    30
#define _PyLong_DECIMAL_SHIFT   9 /* max(e such that 10**e fits in a digit) */
#define _PyLong_DECIMAL_BASE    ((digit)1000000000) /* 10 ** DECIMAL_SHIFT */
#elif PYLONG_BITS_IN_DIGIT == 15
typedef unsigned short digit;
typedef short sdigit; /* signed variant of digit */
typedef unsigned long twodigits;
typedef long stwodigits; /* signed variant of twodigits */
#define PyLong_SHIFT    15
#define _PyLong_DECIMAL_SHIFT   4 /* max(e such that 10**e fits in a digit) */
#define _PyLong_DECIMAL_BASE    ((digit)10000) /* 10 ** DECIMAL_SHIFT */

我把源代码里的 PYLONG_BITS_IN_DIGIT 的值写死成了 15, 这样我下面所有的示例都是以 unsigned short 定义的 digit

当我们需要表示整数 1 的时候, ob_size 的值变成了1, 这个时候 ob_size 表示 ob_digit 的长度, 并且 ob_digit 里以 unsigned short 的表示方式存储了整数1

i = 1

整数 -1

在这里插入图片描述

当 i 变成 -1 时候, 唯一的和整数 1 的区别就是储存在 ob_size 里的值变成了 -1, 这里的负号表示这个整数的正负性, 不影响到 ob_digit 里面的值

i = -1

整数 1023

在这里插入图片描述

对于 PyLongObject 来说, 最基本的存储单位是 digit, 在我这里是 2个 byte 的大小(16个bit). 1023 只需要占用最右边的10个bit 就够了, 所以 ob_size 里的值仍然是 1

整数 32767

在这里插入图片描述

整数 32768

在这里插入图片描述

我们发现, cpython 并不会占用掉一个 digit 的所有的 bit 去存储一个数, 第一个 bit 会被保留下来, 我们后面会看到这个保留下来的 bit 有什么作用

小端大端

注意, 因为 digit 作为 cpython 表示整型的最小存储单元, digit 里面的 byte 存储的顺序和你的机器的顺序一致

digitdigit 之间则是按照 权重最重的 digit 在最右边 原则存储的(小端存储)

我们来看一看整数值 -262143 的存储方式

负号依旧存储在 ob_size 里面

整数值 262143(2^18 = 262144) 的二进制表示应该是 00000011 11111111 11111111

在这里插入图片描述

第一个保留位

为什么 digit 的最左边一位需要保留下来呢? 为什么 ob_digit 里面的 digit 的顺序是按照小端的顺序存储的呢?

我们尝试跑一个简单的加法来理解一下

i = 1073741824 - 1 # 1 << 30 == 1073741824
j = 1

在这里插入图片描述

k = i + j

首先, 相加之前会初始化一个中间变量我这里叫做 temp, 他的类型也是 PyLongObject, 并且大小为 max(size(i), size(j)) + 1
在这里插入图片描述

第一步, 把两个数 ob_digit 里的第一个坑位里的 digit 加起来, 并加到 carry

在这里插入图片描述

第二步, 把 temp[0] 里的值设置为 (carry & PyLong_MASK)

在这里插入图片描述

第三步, 右移 carry, 值保留下最左边的一位(这一位其实就是之前两个数相加的进位)
在这里插入图片描述

第四步, 把两边下一个 ob_digit 的对应位置的值加起来, 并把结果与 carry 相加

在这里插入图片描述

第五步, 把 temp[1] 里的值设置为 (carry & PyLong_MASK)
在这里插入图片描述

第六步, 再次右移
在这里插入图片描述

回到步骤四, 直到两边都没有剩余的 digit 可以相加为止, 把最后的 carry 存储到 temp 最后一格

在这里插入图片描述

temp 这个变量此时按照 PyLongObject 的方式存储了前面两个数的和, 现在细心地你应该发现了, 第一个保留位是为了用来相加或者相减的时候作进位/退位 用的, 并且当 digitdigit 之间按照小端的方式存储的时候, 你做加减法的时候只需要从左到右处理即可

减法的操作和加法类似, 有兴趣的同学可以自己参考源代码

在这里插入图片描述

small ints

cpython 同时也使用了一个全局变量叫做 small_ints 来单例化一部分常见范围的整数, 这么做可以减少频繁的向操作系统申请和释放的次数, 并提高性能


#define NSMALLPOSINTS           257
#define NSMALLNEGINTS           5
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

我们来看看

c = 0
d = 0
e = 0
print(id(c), id(d), id(e)) # 4480940400 4480940400 4480940400
a = -5
b = -5
print(id(a), id(b)) # 4480940240 4480940240
f = 1313131313131313
g = 1313131313131313
print(id(f), id(g)) # 4484604176 4484604016

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CPythonPython 的官方实现,它是用 C 语言实现的。如果你安装了 Python,那么你已经安装了 CPython。你可以在命令行中输入 `python` 来启动 CPython 解释器。 使用 CPython,你可以编写 Python 代码,并执行它们。你还可以使用 C 或 C++ 编写 Python 扩展模块,以扩展 Python 的功能。为了编写扩展模块,你需要使用 Python 的 C API,这些 API 允许你在 C 或 C++ 中操作 Python 对象。 下面是一个使用 CPython 的示例: ```python # hello.py print("Hello, world!") ``` 你可以在命令行中使用以下命令来执行 hello.py: ``` $ python hello.py Hello, world! ``` 如果你想编写 Python 扩展模块,那么你需要使用 CPython 的 C API。一个简单的示例是使用 CPython 的 C API 来编写一个函数,将两个 Python 整数相加,并返回结果。 ```c // add.c #include <Python.h> static PyObject *add(PyObject *self, PyObject *args) { int a, b; if (!PyArg_ParseTuple(args, "ii", &a, &b)) { return NULL; } return PyLong_FromLong(a + b); } static PyMethodDef methods[] = { {"add", add, METH_VARARGS, "Add two integers."}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, "my_module", "My custom module.", -1, methods }; PyMODINIT_FUNC PyInit_my_module(void) { return PyModule_Create(&module); } ``` 你可以使用以下命令将其编译为共享库: ``` $ gcc -shared -o my_module.so add.c $(python3-config --ldflags) ``` 然后,你可以在 Python 中使用它: ```python # main.py import my_module result = my_module.add(1, 2) print(result) ``` 你可以在命令行中使用以下命令来执行 main.py: ``` $ python main.py 3 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值