第 10 讲:数字类型实现——int 和 float 的 C 结构体
第 10 讲:数字类型实现——int 和 float 的 C 结构体

大家好,我是正在实战各种 AI 项目的程序员晚枫。

Python 的 int 可以无限大,float 是双精度,它们在 C 中如何实现?这一讲,揭开数字类型的底层秘密。


📖 开篇:为什么 Python 的 int 没有上限?

在 C 语言里,int 是有限制的:

1
2
int i = 2147483647;
i = i + 1; // 溢出!变成 -2147483648

但在 Python 里:

1
2
i = 2147483647
print(i + 1) # 2147483648,完全正常!

秘密在于:Python 的整数不是 C 的 int,而是变长数组


🔢 PyLongObject(整数)

1
2
3
4
5
6
7
8
// Include/longintrepr.h
struct _longobject {
PyObject_HEAD
digit ob_digit[1]; // 变长数组存储数字
};

// Python 3 用 2^30 进制存储大整数
#define PyLong_SHIFT 30

数字如何存储?

Python 用「30位进制」存储大整数。每个 digit 占 30 位(某些平台是 15 位):

1
2
3
# 假设存储数字 2^60 = 1152921504606846976
# 拆分成 30 位一段:
# [0, 0, 1] → 三段:[低位..., 中位, 高位]

这就解释了为什么 2**602**30 * 2**30 速度不同——大整数的位数越多,运算越慢。


🧠 小整数缓存

Python 在启动时预创建了 -5 到 256 的整数对象:

1
2
3
4
// Objects/longobject.c
#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
static PyLongObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

验证一下:

1
2
3
4
5
6
7
a = 256
b = 256
print(a is b) # True!因为是同一个对象

a = 257
b = 257
print(a is b) # False!超过缓存范围

性能提示:判断整数是否相等用 ==,判断是否是同一对象用 is。不要用 is 比较普通整数大小!


💧 PyFloatObject(浮点数)

1
2
3
4
5
// Include/floatobject.h
typedef struct {
PyObject_HEAD
double ob_fval; // C double 类型,64位双精度
} PyFloatObject;

浮点数使用标准的 IEEE 754 双精度格式:

  • 1 位符号
  • 11 位指数
  • 52 位尾数
1
2
3
4
5
6
import sys
import struct

x = 3.14159
# 内存中存的就是 IEEE 754 双精度数
print(sys.float_info) # 查看浮点数详细信息

精度陷阱

1
2
3
4
5
6
7
# 经典问题
print(0.1 + 0.2) # 0.30000000000000004!

# 原因:0.1 和 0.2 无法精确表示为二进制小数
# 解决:用 decimal 模块
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.3

🔧 整数运算的底层实现

加法

1
2
3
4
5
6
7
8
9
10
// Objects/longobject.c
static PyObject *
long_add(PyLongObject *a, PyLongObject *b)
{
// 1. 比较位数
// 2. 对齐位数
// 3. 按位相加
// 4. 处理进位
// 5. 处理溢出(进位产生新 digit)
}

乘法(Karatsuba 算法)

Python 3 的乘法使用 Karatsuba 分治算法,时间复杂度从 O(n²) 降到 O(n^1.585):

1
2
3
4
5
6
# 两个大整数相乘的字节码
def mul_demo(a, b):
return a * b

import dis
dis.dis(mul_demo)

位运算

1
2
3
x = 1 << 1000  # 2 的 1000 次方,毫无压力!
y = x & (x - 1) # 消除最低位的 1
print(bin(y)) # 验证结果

📊 为什么小整数比大整数快?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time

# 小整数
start = time.perf_counter()
for _ in range(10**7):
x = 1 + 2
end = time.perf_counter()
print(f"小整数加法: {end - start:.3f}s")

# 大整数
start = time.perf_counter()
BIG = 10**100
for _ in range(10**5):
x = BIG + BIG
end = time.perf_counter()
print(f"大整数加法: {end - start:.3f}s")

原因

  1. 小整数在缓存中,不需要分配内存
  2. 小整数的 digit 数组长度 = 1,不需要进位处理
  3. 大整数的位数越多,乘法复杂度越高

💡 本节作业

  1. sys.getsizeof() 比较不同大小整数的内存占用
  2. 验证 is 比较在小整数(100)和大整数(1000)上的差异
  3. 思考:为什么 2**1002**10 * 2**10 快?

🎯 本讲总结

PyLongObject:变长数组存储大整数,每段 30 位。

小整数缓存:-5 到 256 的整数被预创建,is 比较返回 True。

PyFloatObject:IEEE 754 双精度,精度陷阱需用 decimal 处理。

性能真相:小整数运算快是因为有缓存且位数少。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》


🔗 课程导航

上一讲:常见字节码指令 | 下一讲:字符串类型实现


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!