Skip to content

python 基础问答

参考:devv

Q1: 为什么python的整数类型可以做到任意大小

Python 的整数类型(int)能够做到任意大小(任意精度、bignum)主要是因为它内部采用了可变长度数组结构来存储数字,并使用基于高进制的“多位”分块表示法,突破了原生固定位宽整数(如32位或64位整数)大小的限制。具体原因和实现机制可以分为以下几点:


1. Python 整数不是固定位数,而是“变长”的结构体

  • Python 的整数对象并非简单存储为一个固定大小的机器整数,而是一个包含多个“数字块(digit)”的数组。
  • 这使得它可以根据数字的大小动态申请内存空间,数字越大,占用的空间越多。
  • 数字存储采用类似链表或动态数组的方式,突破了原本整数固定长度的限制。

2. 以大进制分块存储数字

  • Python并不是把数字以十进制逐位存储,而是将数字转换成以2^30(30位)为基数的一组整数块。
  • 每个“digit”是一个 30 位大小的无符号整数(通常以uint32_t类型存储)。
  • 举例,数字 N 会被拆成多个“digit”:
    N = d_0 * (2^30)^0 + d_1 * (2^30)^1 + d_2 * (2^30)^2 + ...
    其中每个 d_i 都小于 2^30。
  • 这种表示能大幅度压缩数字的存储,提升运算速度,因为每个块占用较多位。

3. 利用变量长度数组和带符号的长度来表示数字大小与符号

  • Python中的int对象底层定义了一个结构体,包含一个动态长度数组(digit数组)和一个表示符号的长度(ob_size)。
  • ob_size为正表示正数,为负表示负数,0表示数字0。
  • 这个设计允许既存储数值,也隐含了符号信息。

4. 数学运算通过模拟“多位进制”加法实现

  • 加法、减法等操作在底层是基于上述数组逐位模拟手工计算,逐位进位(借位)完成。
  • 乘法等运算也类似,模拟多位进制的乘法。

5. 总结

关键点作用
可变长度数组根据数字大小动态扩展,突破固定位宽限制
以2^30为基的“digit”数组内存高效利用,快速运算
ob_size带符号长度表示数字大小和正负
手工模拟多进制加减乘除保证任意大数字的数学运算准确性

可以说,Python的整数任意精度能力是通过“多位分块存储 + 变长数组 + 仿真算术运算”实现的,这让Python整数理论上只受限于内存大小而不是固定的位数限制。


如果你感兴趣,深入阅读CPython中longobject.c及相关源码,可以看到这些细节的实现。也可以参考这篇讲解文章,内容详尽且通俗:How python implements super long integers?23

需要我帮你整理一份Python内部整数字节结构的示意吗?

Q2: python 复数 -1的平方根

Python 计算 -1 的平方根可以使用 cmath 模块中的 cmath.sqrt() 函数。

python
import cmath

z = -1
sqrt_z = cmath.sqrt(z)
print(sqrt_z)  # 输出 1j

cmath.sqrt() 函数专门用于处理复数的平方根运算。结果 1j 表示虚数单位 i (在 Python 中用 jJ 表示)。

解释:

-1 的平方根在实数范围内无解,但在复数范围内,它的平方根是 i-i,因为 i * i = -1(-i) * (-i) = -1。 Python 默认返回正的虚数根 1j

一些补充说明:

  • 不要使用 math.sqrt(),因为它只适用于实数,对负数会报错。
  • jJ 后面可以直接跟数字表示虚部,例如 3j 表示 3i2.5J 表示 2.5i
  • 复数可以用 a + bj 的形式表示,其中 a 是实部,b 是虚部。

希望这个解答对你有帮助!

Q3: (1+2j) * (3+4j) 等于 (-5+10j)复数乘法计算过程是怎么样的

复数乘法遵循分配律,类似于多项式乘法,并利用 $i^2 = -1$ 的特性进行简化。以下是 (1+2j) * (3+4j) 的详细计算步骤:

  1. 分配律展开: (1+2j) * (3+4j) = 1*(3+4j) + 2j*(3+4j)

  2. 继续展开: = 13 + 14j + 2j3 + 2j4j = 3 + 4j + 6j + 8j^2

  3. 利用 $j^2 = -1$ 化简: = 3 + 4j + 6j + 8*(-1) = 3 + 4j + 6j - 8

  4. 合并同类项: = (3 - 8) + (4j + 6j) = -5 + 10j

因此,(1+2j) * (3+4j) = -5 + 10j。

一般公式:

两个复数 (a + bj) 和 (c + dj) 相乘的公式为:

(a + bj) * (c + dj) = (ac - bd) + (ad + bc)j

在这个例子中:

a = 1, b = 2 c = 3, d = 4

(1 + 2j) * (3 + 4j) = (13 - 24) + (14 + 23)j = (3 - 8) + (4 + 6)j = -5 + 10j

希望这个解释能够帮助你理解复数乘法的计算过程。

Q4: 载入math模块并测试一些函数,然后载入cmath模块并执行同名函数。当载入cmath模块后,对整数或浮点数调用其中的函数,会产生什么结果?怎样才能让math模块的函数重新可用呢?

python
import math
import cmath

# 测试 math 模块的函数
print(math.sqrt(4))  # 输出 2.0
print(math.sin(math.pi/2))  # 输出 1.0

# 载入 cmath 模块
# 现在 math 和 cmath 模块中的同名函数都可用

# 对整数或浮点数调用 cmath 模块中的函数
print(cmath.sqrt(4))  # 输出 (2+0j)
print(cmath.sin(math.pi/2))  # 输出 (1+0j)

# 观察结果:cmath 模块的函数将整数和浮点数转换为复数并进行运算,返回复数结果。

# 想要让 math 模块的函数重新可用,可以直接调用它们,因为它们仍然在命名空间中。
print(math.sqrt(4))  # 仍然输出 2.0

# 或者,可以给 math 模块中的函数赋予新的名称:
math_sqrt = math.sqrt
print(math_sqrt(4))  # 输出 2.0


# 如果你想完全移除 cmath 模块的影响,可以使用 del 命令,但这并不推荐,因为它可能会导致其他问题。
# del cmath  # 不推荐

# 最佳实践是明确地使用模块名来调用函数,例如 math.sqrt() 或 cmath.sqrt(),以避免混淆。

总结:

  1. 载入 cmath 模块后,math 模块的同名函数仍然可用。
  2. 使用 cmath 模块中的函数对整数或浮点数进行运算,会返回复数结果,即使结果的虚部为零。
  3. 最佳实践是使用 模块名.函数名() 的方式调用函数,例如 math.sqrt()cmath.sqrt(),以避免歧义和潜在的错误。

希望这个解释和示例能够解答你的问题。