嘿,朋友!今天咱们不聊那些让人头秃的微积分公式,而是来聊聊 Python 里一个既优雅又实用的数据类型——复数(Complex Numbers)。
你可能在数学课上见过 \(a + bi\) 这种写法,当时觉得“这玩意儿有啥用?考试又不考”。但相信我,一旦你进入信号处理、电气工程、游戏开发或者物理模拟的领域,复数简直就是你的“秘密武器”。而且,Python 对复数的支持好到让你惊讶——你甚至不需要导入任何库,内置语法就够用了。
这篇文章,我会把复数的加减乘除掰开了、揉碎了讲给你听。无论你是刚入门的小白,还是想复习一下底层原理的老手,都能找到有用的东西。咱们从最基础的开始,一步步走到实战应用。
一、Python 里的复数长什么样?
首先,咱们得认识复数在 Python 里是怎么表示的。
1.1 基本语法
Python 中,虚数单位用 j 或 J 表示(注意:不是 i!这是工程学的惯例,数学里常用 i,但 Python 追随了电气工程的传统)。
# 定义一个复数:3 + 4j
z1 = 3 + 4j
# 定义另一个复数:1 - 2j
z2 = 1 - 2j
print(z1) # 输出: (3+4j)
print(z2) # 输出: (1-2j)
# 检查类型
print(type(z1)) # 输出: <class 'complex'>
1.2 复数的组成部分
每个复数都有两个部分:
- 实部(Real Part):
z.real - 虚部(Imaginary Part):
z.imag
z = 3 + 4j
print(z.real) # 输出: 3.0
print(z.imag) # 输出: 4.0
# 复数的模(Magnitude),也就是它到原点的距离
print(abs(z)) # 输出: 5.0
# 因为 sqrt(3² + 4²) = sqrt(9 + 16) = sqrt(25) = 5
1.3 复数的共轭
共轭复数就是把虚部的符号取反。比如 \(3 + 4j\) 的共轭是 \(3 - 4j\)。
z = 3 + 4j
print(z.conjugate()) # 输出: (3-4j)
共轭复数有个重要性质:\(z \times \bar{z} = |z|^2\)(实数)。这在除法运算中会用到。
二、复数的加减法
加减法是最直观的,因为你可以分别对实部和虚部进行运算。
2.1 数学原理
假设有两个复数:
- \(z_1 = a + bj\)
- \(z_2 = c + dj\)
那么:
- 加法:\(z_1 + z_2 = (a + c) + (b + d)j\)
- 减法:\(z_1 - z_2 = (a - c) + (b - d)j\)
是不是很简单?就是实部加减实部,虚部加减虚部。
2.2 Python 代码实现
def complex_add(z1, z2):
"""复数加法"""
return z1 + z2
def complex_sub(z1, z2):
"""复数减法"""
return z1 - z2
# 测试
z1 = 3 + 4j
z2 = 1 - 2j
print(f"z1 = {z1}")
print(f"z2 = {z2}")
print(f"z1 + z2 = {complex_add(z1, z2)}") # 输出: (4+2j)
print(f"z1 - z2 = {complex_sub(z1, z2)}") # 输出: (2+6j)
2.3 手动实现(理解原理)
如果你想深入理解,可以手动实现:
def add_manual(z1, z2):
real_part = z1.real + z2.real
imag_part = z1.imag + z2.imag
return complex(real_part, imag_part)
def sub_manual(z1, z2):
real_part = z1.real - z2.real
imag_part = z1.imag - z2.imag
return complex(real_part, imag_part)
z1 = 3 + 4j
z2 = 1 - 2j
print(add_manual(z1, z2)) # 输出: (4+2j)
print(sub_manual(z1, z2)) # 输出: (2+6j)
小贴士:Python 的
complex()构造函数可以接受两个参数,第一个是实部,第二个是虚部。
三、复数的乘法
乘法稍微复杂一点,但别担心,我们有公式可以用。
3.1 数学原理
假设有两个复数:
- \(z_1 = a + bj\)
- \(z_2 = c + dj\)
乘法展开: $\(z_1 \times z_2 = (a + bj)(c + dj) = ac + adj + bjc + bjd^2\)$
因为 \(j^2 = -1\),所以: $\(z_1 \times z_2 = (ac - bd) + (ad + bc)j\)$
3.2 Python 代码实现
def complex_mul(z1, z2):
"""复数乘法"""
# Python 内置支持
return z1 * z2
# 测试
z1 = 3 + 4j
z2 = 1 - 2j
print(f"z1 * z2 = {complex_mul(z1, z2)}")
# 手动验证:
# 实部 = 3*1 - 4*(-2) = 3 + 8 = 11
# 虚部 = 3*(-2) + 4*1 = -6 + 4 = -2
# 所以结果应该是 11 - 2j
print(f"验证:11 - 2j = {11 - 2j}")
3.3 手动实现(加深理解)
def mul_manual(z1, z2):
"""手动实现复数乘法"""
a = z1.real
b = z1.imag
c = z2.real
d = z2.imag
real_part = a * c - b * d
imag_part = a * d + b * c
return complex(real_part, imag_part)
z1 = 3 + 4j
z2 = 1 - 2j
result = mul_manual(z1, z2)
print(f"手动乘法结果:{result}") # 输出: (11-2j)
3.4 一个有趣的性质:旋转
复数乘法在几何上代表旋转和缩放。比如,乘以 \(j\) 相当于逆时针旋转 90 度。
z = 1 + 0j # 在实轴上的点 (1, 0)
print(z * 1j) # 输出: 1j,相当于旋转到了 (0, 1)
z2 = 1j * 1j # 再旋转 90 度
print(z2) # 输出: (-1+0j),相当于旋转到了 (-1, 0)
这个性质在图形学和游戏开发中非常有用!
四、复数的除法
除法是四个运算中最复杂的,因为我们需要用到共轭复数来有理化分母。
4.1 数学原理
假设有两个复数:
- \(z_1 = a + bj\)
- \(z_2 = c + dj\)
我们要计算 \(\frac{z_1}{z_2}\):
\[\frac{a + bj}{c + dj} = \frac{(a + bj)(c - dj)}{(c + dj)(c - dj)}\]
分母变成: $\((c + dj)(c - dj) = c^2 - (dj)^2 = c^2 + d^2\)$
分子变成: $\((a + bj)(c - dj) = (ac + bd) + (bc - ad)j\)$
所以: $\(\frac{z_1}{z_2} = \frac{ac + bd}{c^2 + d^2} + \frac{bc - ad}{c^2 + d^2}j\)$
4.2 Python 代码实现
def complex_div(z1, z2):
"""复数除法"""
# Python 内置支持
return z1 / z2
# 测试
z1 = 3 + 4j
z2 = 1 - 2j
result = complex_div(z1, z2)
print(f"z1 / z2 = {result}")
# 手动验证:
# 分母 = 1² + (-2)² = 1 + 4 = 5
# 实部 = (3*1 + 4*2) / 5 = (3 + 8) / 5 = 11/5 = 2.2
# 虚部 = (4*1 - 3*2) / 5 = (4 - 6) / 5 = -2/5 = -0.4
# 所以结果应该是 2.2 - 0.4j
print(f"验证:2.2 - 0.4j = {2.2 - 0.4j}")
4.3 手动实现(理解分母有理化)
def div_manual(z1, z2):
"""手动实现复数除法"""
a = z1.real
b = z1.imag
c = z2.real
d = z2.imag
# 分母:c² + d²
denominator = c**2 + d**2
# 分子:(a + bj)(c - dj)
real_part = (a * c + b * d) / denominator
imag_part = (b * c - a * d) / denominator
return complex(real_part, imag_part)
z1 = 3 + 4j
z2 = 1 - 2j
result = div_manual(z1, z2)
print(f"手动除法结果:{result}") # 输出: (2.2-0.4j)
4.4 处理除零的情况
除数是 0 的情况需要特别处理:
def safe_div(z1, z2, epsilon=1e-10):
"""安全的复数除法"""
# 检查除数是否接近零
if abs(z2) < epsilon:
raise ZeroDivisionError("除数不能为零")
return z1 / z2
try:
result = safe_div(3 + 4j, 0 + 0j)
except ZeroDivisionError as e:
print(e) # 输出: 除数不能为零
五、综合示例:复数运算类
把加减乘除封装成一个类,更方便使用:
class ComplexNumber:
"""复数运算类"""
def __init__(self, real, imag):
self.real = real
self.imag = imag
self.complex = complex(real, imag)
def __add__(self, other):
"""加法"""
if isinstance(other, (int, float)):
other = ComplexNumber(other, 0)
return ComplexNumber(
self.real + other.real,
self.imag + other.imag
)
def __sub__(self, other):
"""减法"""
if isinstance(other, (int, float)):
other = ComplexNumber(other, 0)
return ComplexNumber(
self.real - other.real,
self.imag - other.imag
)
def __mul__(self, other):
"""乘法"""
if isinstance(other, (int, float)):
other = ComplexNumber(other, 0)
real_part = self.real * other.real - self.imag * other.imag
imag_part = self.real * other.imag + self.imag * other.real
return ComplexNumber(real_part, imag_part)
def __truediv__(self, other):
"""除法"""
if isinstance(other, (int, float)):
other = ComplexNumber(other, 0)
denominator = other.real**2 + other.imag**2
if denominator == 0:
raise ZeroDivisionError("除数不能为零")
real_part = (self.real * other.real + self.imag * other.imag) / denominator
imag_part = (self.imag * other.real - self.real * other.imag) / denominator
return ComplexNumber(real_part, imag_part)
def __str__(self):
if self.imag >= 0:
return f"{self.real} + {self.imag}j"
else:
return f"{self.real} - {abs(self.imag)}j"
def modulus(self):
"""模"""
return (self.real**2 + self.imag**2) ** 0.5
def conjugate(self):
"""共轭"""
return ComplexNumber(self.real, -self.imag)
# 测试
c1 = ComplexNumber(3, 4)
c2 = ComplexNumber(1, -2)
print(f"c1 = {c1}") # 输出: 3 + 4j
print(f"c2 = {c2}") # 输出: 1 - 2j
print(f"c1 + c2 = {c1 + c2}") # 输出: 4 + 2j
print(f"c1 - c2 = {c1 - c2}") # 输出: 2 + 6j
print(f"c1 * c2 = {c1 * c2}") # 输出: 11 - 2j
print(f"c1 / c2 = {c1 / c2}") # 输出: 2.2 - 0.4j
print(f"|c1| = {c1.modulus()}") # 输出: 5.0
print(f"共轭(c1) = {c1.conjugate()}") # 输出: 3 - 4j
这个类展示了如何重载运算符,让复数运算看起来像自然语言一样流畅。
六、实战应用:复数能干什么?
现在,咱们来看看复数在实际项目中的应用。别以为这只是数学游戏,它在很多领域都有大用场。
6.1 信号处理:快速傅里叶变换(FFT)
傅里叶变换是信号处理的核心,它把时域信号转换成频域信号。而 FFT 是傅里叶变换的高效算法,广泛应用于音频处理、图像分析、通信等领域。
import numpy as np
from scipy.fft import fft, ifft
import matplotlib.pyplot as plt
# 生成一个包含两个频率的正弦波信号
sample_rate = 1000 # 采样率
duration = 1.0 # 持续时间
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
# 50 Hz 和 120 Hz 的正弦波叠加
signal = np.sin(2 * np.pi * 50 * t) + np.sin(2 * np.pi * 120 * t)
# 添加一些噪声
noise = 0.5 * np.random.randn(len(t))
signal_noisy = signal + noise
# 进行傅里叶变换
fft_result = fft(signal_noisy)
# 计算频率轴
freqs = np.fft.fftfreq(len(signal_noisy), 1/sample_rate)
# 只取正频率部分
positive_freqs = freqs[:len(freqs)//2]
positive_magnitude = 2.0 / len(signal_noisy) * np.abs(fft_result[:len(fft_result)//2])
# 绘图
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(t, signal_noisy)
plt.title('原始信号(含噪声)')
plt.xlabel('时间 (s)')
plt.ylabel('振幅')
plt.subplot(1, 2, 2)
plt.plot(positive_freqs, positive_magnitude)
plt.title('频谱分析')
plt.xlabel('频率 (Hz)')
plt.ylabel('振幅')
plt.tight_layout()
plt.show()
print("在频谱图中,你可以清楚地看到两个峰值:50 Hz 和 120 Hz")
print("这就是傅里叶变换的力量——把时域信号转换成频域信号")
为什么用复数? 傅里叶变换的结果是复数,实部表示余弦分量,虚部表示正弦分量。复数的模表示振幅,幅角表示相位。
6.2 电气工程:交流电路分析
在交流电路中,电压和电流可以用复数表示(相量法),简化计算。
”`python class AC_Circuit:
"""交流电路分析"""
def __init__(self, voltage, frequency, resistance, inductance, capacitance):
self.voltage = voltage # 电压(复数,比如 220 + 0j 表示 220V 实部)
self.frequency = frequency # 频率(Hz)
self.resistance = resistance # 电阻(欧姆)
self.inductance = inductance # 电感(亨利)
self.capacitance =
