NumPy: 数学函数
1. 你将学到
- ❶ 三角函数 —
np.sin,np.cos,np.tan及反三角 - ❷ 角度弧度转换 —
np.radians,np.degrees - ❸ 指数对数 —
np.exp,np.log,np.log2,np.log10 - ❹ 取整 —
np.round,np.floor,np.ceil,np.rint,np.trunc - ❺ sign/abs/clip —
np.sign,np.abs,np.clip,np.hypot
2. 故事
Bob 用 math.sin 对 10 万角度求正弦,耗时 0.3 秒。Alice 用 np.sin 3 毫秒就完成了——
"math.sin 处理 1 个,np.sin 处理 10 万个,别忘了
np.radians()转换。"
Bob 恍然大悟:Python 标准库的数学函数只对标量,NumPy 的数学函数才是批量处理的利器。
3. 知识点
(1) 三角函数
NumPy 提供完整的三角函数族,参数一律为弧度:
PYTHON
import numpy as np
angles = np.array([0, 30, 45, 60, 90])
rad = np.radians(angles)
print("弧度:", rad)
print("sin:", np.sin(rad))
print("cos:", np.cos(rad))
print("tan:", np.tan(rad))
TEXT
📖 仅展示
弧度: [0. 0.5236 0.7854 1.0472 1.5708]
sin: [0. 0.5 0.7071 0.866 1. ]
cos: [1. 0.866 0.7071 0.5 0. ]
tan: [0. 0.5774 1. 1.7321 1.6331e+16]
反三角函数返回弧度值:
PYTHON
print("arcsin(0.5) =", np.arcsin(0.5)) # 0.5236 rad
print("arccos(0.5) =", np.arccos(0.5)) # 1.0472 rad
print("arctan(1.0) =", np.arctan(1.0)) # 0.7854 rad
print("转为角度:", np.degrees(np.arcsin(0.5))) # 30.0
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
np.arctan2(y, x) 根据坐标自动判断象限:
PYTHON
print(np.degrees(np.arctan2(1, 1))) # 45.0
print(np.degrees(np.arctan2(-1, -1))) # -135.0 (第三象限)
print(np.degrees(np.arctan2(1, -1))) # 135.0 (第二象限)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) 角度弧度转换
| 函数 | 说明 | 公式 |
|---|---|---|
np.radians(deg) |
角度 → 弧度 | rad = deg × π / 180 |
np.degrees(rad) |
弧度 → 角度 | deg = rad × 180 / π |
np.rad2deg(rad) |
同 np.degrees |
同上 |
np.deg2rad(deg) |
同 np.radians |
同上 |
PYTHON
import numpy as np
deg = np.array([0, 45, 90, 180, 270, 360])
rad = np.radians(deg)
back = np.degrees(rad)
print("角度:", deg)
print("弧度:", rad.round(4))
print("还原:", back.round(4))
print("π 常量:", np.pi)
TEXT
📖 仅展示
角度: [ 0 45 90 180 270 360]
弧度: [0. 0.7854 1.5708 3.1416 4.7124 6.2832]
还原: [ 0 45 90 180 270 360]
π 常量: 3.141592653589793
(3) 指数与对数
PYTHON
import numpy as np
x = np.array([0, 1, 2, 3])
print("e^x:", np.exp(x))
print("2^x:", np.exp2(x))
print("10^x:", np.power(10, x))
print("ln(x+1):", np.log1p(x))
TEXT
📖 仅展示
e^x: [ 1. 2.7183 7.3891 20.0855]
2^x: [1. 2. 4. 8.]
10^x: [ 1. 10. 100. 1000.]
ln(x+1): [0. 0.6931 1.0986 1.3863]
| 函数 | 说明 | 公式 |
|---|---|---|
np.exp(x) |
e 的 x 次幂 | e^x |
np.exp2(x) |
2 的 x 次幂 | 2^x |
np.log(x) |
自然对数 | ln(x) |
np.log2(x) |
以 2 为底对数 | log₂(x) |
np.log10(x) |
以 10 为底对数 | log₁₀(x) |
np.log1p(x) |
ln(x+1) | 对小 x 精度更高 |
np.expm1(x) |
e^x - 1 | 对小 x 精度更高 |
(4) 取整函数
PYTHON
import numpy as np
a = np.array([-2.7, -1.5, -0.3, 0.3, 1.5, 2.7])
print("round:", np.round(a))
print("floor:", np.floor(a))
print("ceil: ", np.ceil(a))
print("rint: ", np.rint(a))
print("trunc:", np.trunc(a))
TEXT
📖 仅展示
round: [-3. -2. -0. 0. 2. 3.]
floor: [-3. -2. -1. 0. 1. 2.]
ceil: [-2. -1. 0. 1. 2. 3.]
rint: [-3. -2. -0. 0. 2. 3.]
trunc: [-2. -1. -0. 0. 1. 2.]
| 函数 | 说明 | -2.7 | -1.5 | 1.5 | 2.7 |
|---|---|---|---|---|---|
np.round |
四舍五入(银行家舍入) | -3 | -2 | 2 | 3 |
np.floor |
向负无穷取整 | -3 | -2 | 1 | 2 |
np.ceil |
向正无穷取整 | -2 | -1 | 2 | 3 |
np.rint |
就近取整(同 round) | -3 | -2 | 2 | 3 |
np.trunc |
截断小数(向零取整) | -2 | -1 | 1 | 2 |
(5) sign / abs / hypot / clip
PYTHON
import numpy as np
a = np.array([-3.5, -1, 0, 2, 5.7])
print("sign:", np.sign(a))
print("abs: ", np.abs(a))
x = np.array([3, 5, 8])
y = np.array([4, 12, 15])
print("hypot:", np.hypot(x, y))
signal = np.array([-2.5, -0.3, 0.7, 3.8])
print("clip:", np.clip(signal, -1, 1))
TEXT
📖 仅展示
sign: [-1. -1. 0. 1. 1.]
abs: [3.5 1. 0. 2. 5.7]
hypot: [ 5. 13. 17.]
clip: [-1. -0.3 0.7 1. ]
| 函数 | 说明 |
|---|---|
np.sign(x) |
返回 -1 / 0 / 1 |
np.abs(x) |
绝对值 |
np.hypot(x, y) |
√(x² + y²),即欧氏距离 |
np.clip(x, lo, hi) |
限制在 [lo, hi] 范围 |
4. 对比表格
(1) math vs np 对照
| 维度 | math.sin |
np.sin |
|---|---|---|
| 输入 | 单个标量 | 数组或标量 |
| 返回 | 单个 float | 同形状数组 |
| 速度(10万元素) | ~0.3 s(需循环) | ~3 ms(向量化) |
| 广播 | 无 | 有 |
| 类型 | 仅 float | 支持 float32/64 等 |
(2) 三角速查
| 函数 | 说明 | 反函数 |
|---|---|---|
np.sin |
正弦 | np.arcsin |
np.cos |
余弦 | np.arccos |
np.tan |
正切 | np.arctan / np.arctan2 |
np.radians |
角度→弧度 | np.degrees |
(3) 指数对数速查
| 函数 | 公式 | 用途 |
|---|---|---|
np.exp |
e^x | 增长模型、概率 |
np.log |
ln(x) | 信息论、似然 |
np.log10 |
log₁₀(x) | 分贝、pH值 |
np.log2 |
log₂(x) | 信息熵 |
np.log1p |
ln(x+1) | 小值高精度 |
np.expm1 |
e^x-1 | 小值高精度 |
(4) 取整方式对比
| 函数 | -2.7 | -1.5 | 1.5 | 2.7 | 舍入方向 |
|---|---|---|---|---|---|
round |
-3 | -2 | 2 | 3 | 最近偶数 |
floor |
-3 | -2 | 1 | 2 | -∞ |
ceil |
-2 | -1 | 2 | 3 | +∞ |
trunc |
-2 | -1 | 1 | 2 | 零 |
5. 原理图解
graph TB
A["NumPy 数学函数"] --> B["三角函数<br>sin/cos/tan<br>arcsin/arccos/arctan"]
A --> C["角度弧度转换<br>radians/degrees"]
A --> D["指数对数<br>exp/log/log2/log10<br>log1p/expm1"]
A --> E["取整<br>round/floor/ceil<br>rint/trunc"]
A --> F["符号与范围<br>sign/abs/clip<br>hypot"]
B --> C
B --> G["arctan2(y,x)<br>自动判断象限"]
D --> H["log1p/expm1<br>小值高精度"]
E --> I["round 银行家舍入<br>0.5→最近偶数"]
F --> J["clip(x,lo,hi)<br>信号限幅"]
style A fill:#e1f5fe
style B fill:#fff9c4
style D fill:#c8e6c9
style E fill:#ffccbc
style F fill:#e1bee7
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
6. 示例
▶ 示例: 1:三角函数与弧度转换(难度⭐)
PYTHON
import numpy as np
angles = np.array([0, 30, 45, 60, 90, 180, 270])
rad = np.radians(angles)
print("角度:", angles)
print("弧度:", np.round(rad, 4))
print("sin:", np.round(np.sin(rad), 4))
print("cos:", np.round(np.cos(rad), 4))
back = np.degrees(rad)
print("还原角度:", back)
sin_val = np.sin(rad)
origin = np.degrees(np.arcsin(sin_val[:4]))
print("arcsin 还原:", np.round(origin, 1))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
角度: [ 0 30 45 60 90 180 270]
弧度: [0. 0.5236 0.7854 1.0472 1.5708 3.1416 4.7124]
sin: [0. 0.5 0.7071 0.866 1. 0. -1. ]
cos: [1. 0.866 0.7071 0.5 0. -1. -0. ]
还原角度: [ 0. 30. 45. 60. 90. 180. 270.]
arcsin 还原: [ 0. 30. 45. 60.]
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
▶ 示例: 2:指数与对数(难度⭐)
PYTHON
import numpy as np
x = np.array([0.1, 1, 2, 5, 10])
print("exp(x):", np.exp(x).round(4))
print("log(x):", np.log(x).round(4))
print("log10(x):", np.log10(x).round(4))
print("log2(x):", np.log2(x).round(4))
tiny = np.array([1e-15, 1e-10, 1e-5])
print("\nlog(1+x) vs log1p(x):")
print("log(1+x):", np.log(1 + tiny))
print("log1p(x):", np.log1p(tiny))
print("\nexp(x)-1 vs expm1(x):")
print("exp(x)-1:", np.exp(tiny) - 1)
print("expm1(x):", np.expm1(tiny))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
exp(x): [ 1.1052 2.7183 7.3891 148.4132 22026.4658]
log(x): [-2.3026 0. 0.6931 1.6094 2.3026]
log10(x): [-1. 0. 0.301 0.699 1. ]
log2(x): [-3.3219 0. 1. 2.3219 3.3219]
log(1+x) vs log1p(x):
log(1+x): [1.11022302e-15 1.00000000e-10 1.00000500e-05]
log1p(x): [1.e-15 1.e-10 1.e-05]
exp(x)-1 vs expm1(x):
exp(x)-1: [1.00000000e-15 1.00000000e-10 1.00005000e-05]
expm1(x): [1.e-15 1.e-10 1.00005e-05]
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
▶ 示例: 3:取整方式对比(难度⭐)
PYTHON
import numpy as np
a = np.array([-2.7, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 2.7])
print(f"{'值':>6} {'round':>6} {'floor':>6} {'ceil':>6} {'trunc':>6}")
for v in a:
print(f"{v:>6.1f} {np.round(v):>6.0f} {np.floor(v):>6.0f} {np.ceil(v):>6.0f} {np.trunc(v):>6.0f}")
print("\n银行家舍入验证:")
print("round(0.5) =", np.round(0.5)) # 0 (偶数)
print("round(1.5) =", np.round(1.5)) # 2 (偶数)
print("round(2.5) =", np.round(2.5)) # 2 (偶数)
print("round(3.5) =", np.round(3.5)) # 4 (偶数)
print("\n指定小数位:")
b = np.array([3.14159, 2.71828, 1.41421])
print("round(2):", np.round(b, 2))
print("round(4):", np.round(b, 4))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
值 round floor ceil trunc
-2.7 -3 -3 -2 -2
-2.5 -2 -3 -2 -2
-1.5 -2 -2 -1 -1
-0.5 -0 -1 0 -0
0.5 0 0 1 0
1.5 2 1 2 1
2.5 2 2 3 2
2.7 3 2 3 2
银行家舍入验证:
round(0.5) = 0.0
round(1.5) = 2.0
round(2.5) = 2.0
round(3.5) = 4.0
指定小数位:
round(2): [3.14 2.72 1.41]
round(4): [3.1416 2.7183 1.4142]
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
▶ 示例: 4:sign / abs / clip(难度⭐)
PYTHON
import numpy as np
data = np.array([-5.2, -3, 0, 2, 7.8])
print("sign:", np.sign(data))
print("abs: ", np.abs(data))
x = np.array([3, 5, 8, 1])
y = np.array([4, 12, 15, 1])
print("hypot(x,y):", np.hypot(x, y))
print("sqrt(x^2+y^2):", np.sqrt(x**2 + y**2))
print("结果一致:", np.allclose(np.hypot(x, y), np.sqrt(x**2 + y**2)))
signal = np.array([-3.5, -1.2, -0.5, 0, 0.8, 2.3, 4.1])
print("\n原始信号:", signal)
print("clip[-1,1]:", np.clip(signal, -1, 1))
print("clip[0,3]:", np.clip(signal, 0, 3))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
sign: [-1. -1. 0. 1. 1.]
abs: [5.2 3. 0. 2. 7.8]
hypot(x,y): [ 5. 13. 17. 1.4142]
sqrt(x^2+y^2): [ 5. 13. 17. 1.4142]
结果一致: True
原始信号: [-3.5 -1.2 -0.5 0. 0.8 2.3 4.1]
clip[-1,1]: [-1. -1. -0.5 0. 0.8 1. 1. ]
clip[0,3]: [0. 0. 0. 0. 0.8 2.3 3. ]
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
▶ 示例: 5:正弦波生成(难度⭐⭐)
PYTHON
import numpy as np
t = np.linspace(0, 2 * np.pi, 1000)
freq1 = 1
freq2 = 3
wave1 = np.sin(freq1 * t)
wave2 = 0.5 * np.sin(freq2 * t)
combined = wave1 + wave2
print("时间范围: [0, {:.4f}]".format(t[-1]))
print("wave1 范围: [{:.4f}, {:.4f}]".format(wave1.min(), wave1.max()))
print("wave2 范围: [{:.4f}, {:.4f}]".format(wave2.min(), wave2.max()))
print("combined 范围: [{:.4f}, {:.4f}]".format(combined.min(), combined.max()))
clipped = np.clip(combined, -0.8, 0.8)
print("clip后范围: [{:.4f}, {:.4f}]".format(clipped.min(), clipped.max()))
print("被clip的样本数:", np.sum(np.abs(combined) > 0.8))
sample_points = np.arange(0, 12)
sample_radians = 2 * np.pi * sample_points / 12
clock_angles = np.degrees(sample_radians)
print("\n时钟12点对应角度:", np.round(clock_angles, 1))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
时间范围: [0, 6.2832]
wave1 范围: [-1.0000, 1.0000]
wave2 范围: [-0.5000, 0.5000]
combined 范围: [-1.2658, 1.2658]
clip后范围: [-0.8000, 0.8000]
被clip的样本数: 332
时钟12点对应角度: [ 0. 30. 60. 90. 120. 150. 180. 210. 240. 270. 300. 330.]
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
7. 综合示例:合成信号处理
Alice 用数学函数组合实现信号合成 → 限幅 → 滤波 → 量化,一条流水线完成。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:正弦波+噪声 → clip → 滤波 → 量化(难度⭐⭐⭐)
PYTHON
import numpy as np
rng = np.random.default_rng(42)
# ========================================
# Part 1: 合成信号 — 正弦波 + 噪声
# ========================================
print("=== Part 1: 合成信号 ===\n")
n = 500
t = np.linspace(0, 4 * np.pi, n)
signal = 2.0 * np.sin(t) + 0.8 * np.sin(3 * t)
noise = rng.normal(0, 0.5, size=n)
raw = signal + noise
print("信号范围: [{:.2f}, {:.2f}]".format(signal.min(), signal.max()))
print("含噪范围: [{:.2f}, {:.2f}]".format(raw.min(), raw.max()))
print("SNR: {:.2f} dB".format(
10 * np.log10(np.var(signal) / np.var(noise))))
# ========================================
# Part 2: clip 限幅
# ========================================
print("\n=== Part 2: 限幅 ===\n")
clipped = np.clip(raw, -2.5, 2.5)
n_clipped = np.sum(np.abs(raw) > 2.5)
print("限幅范围: [{:.2f}, {:.2f}]".format(clipped.min(), clipped.max()))
print("被截断样本: {} / {} ({:.1f}%)".format(
n_clipped, n, 100 * n_clipped / n))
# ========================================
# Part 3: 简单移动平均滤波
# ========================================
print("\n=== Part 3: 移动平均滤波 ===\n")
window = 15
kernel = np.ones(window) / window
filtered = np.convolve(clipped, kernel, mode='same')
print("滤波后范围: [{:.2f}, {:.2f}]".format(filtered.min(), filtered.max()))
print("滤波后std: {:.4f} (原始: {:.4f})".format(
filtered.std(), raw.std()))
# ========================================
# Part 4: 量化 — 8级量化
# ========================================
print("\n=== Part 4: 8级量化 ===\n")
levels = 8
step = (filtered.max() - filtered.min()) / levels
quantized = np.floor((filtered - filtered.min()) / step) * step + filtered.min()
quantized = np.round(quantized, 2)
unique_levels = np.unique(quantized)
print("量化步长: {:.4f}".format(step))
print("量化级数: {}".format(len(unique_levels)))
print("量化值:", unique_levels)
quant_error = filtered - quantized
print("量化误差 std: {:.4f}".format(quant_error.std()))
print("量化误差 max: {:.4f}".format(np.abs(quant_error).max()))
# ========================================
# Part 5: 统计汇总
# ========================================
print("\n=== Part 5: 统计汇总 ===\n")
print("原始信号 mean={:.3f} std={:.3f}".format(raw.mean(), raw.std()))
print("限幅信号 mean={:.3f} std={:.3f}".format(clipped.mean(), clipped.std()))
print("滤波信号 mean={:.3f} std={:.3f}".format(filtered.mean(), filtered.std()))
print("量化信号 mean={:.3f} std={:.3f}".format(quantized.mean(), quantized.std()))
输出:
TEXT 📖 仅展示# 输出结果
TEXT
📖 仅展示
=== Part 1: 合成信号 ===
信号范围: [-2.33, 2.33]
含噪范围: [-3.61, 3.49]
SNR: 9.03 dB
=== Part 2: 限幅 ===
限幅范围: [-2.50, 2.50]
被截断样本: 23 / 500 (4.6%)
=== Part 3: 移动平均滤波 ===
滤波后范围: [-2.18, 2.15]
滤波后std: 1.0234 (原始: 1.1587)
=== Part 4: 8级量化 ===
量化步长: 0.5411
量化级数: 9
量化值: [-2.18 -1.64 -1.1 -0.55 -0.01 0.54 1.08 1.62 2.15]
量化误差 std: 0.1561
量化误差 max: 0.2710
=== Part 5: 统计汇总 ===
原始信号 mean=0.013 std=1.159
限幅信号 mean=0.009 std=1.102
滤波信号 mean=0.009 std=1.023
量化信号 mean=0.009 std=1.019
⚠️ 注意: 以上代码需在本地 Python 环境中运行。
❓ 常见问题
Q 三角函数参数是弧度还是角度?
A 弧度。NumPy 所有三角函数的输入和反三角函数的输出均为弧度。用
np.radians(deg) 将角度转为弧度,np.degrees(rad) 将弧度转回角度。直接传角度值是初学者最常见错误。Q log1p 和 log 有什么区别?
A
np.log1p(x) 计算 ln(1+x),当 x 非常小时(如 1e-15),np.log(1+x) 会因浮点精度丢失而结果不准确,np.log1p 内部用泰勒展开保证精度。同理 np.expm1(x) 计算 e^x - 1,比 np.exp(x) - 1 对小 x 更精确。Q np.round 是银行家舍入吗?
A 是的。当值恰好在两整数中间(如 0.5、1.5、2.5),
np.round 向最近的偶数舍入:round(0.5)=0,round(1.5)=2,round(2.5)=2。这与 Python 内置 round() 行为一致,但不同于传统的四舍五入。如需传统四舍五入,可用 np.floor(x + 0.5)。Q clip 和 where 有什么区别?
A
np.clip(x, lo, hi) 将超限值强制拉回边界,输出范围严格在 [lo, hi];np.where(cond, x, y) 是条件选择,满足条件取 x 否则取 y。clip 专用于限幅且更高效;where 更灵活但 clip 能做的 where 也能实现:np.where(x < lo, lo, np.where(x > hi, hi, x))。Q 为什么用 np.sin 不用 math.sin?
A
math.sin 只接受单个标量,对数组需要 Python 循环逐元素调用,速度慢 100 倍以上。np.sin 是 ufunc,底层 C 循环一次性处理整个数组,自动支持广播和类型提升。只处理单个值时两者性能相当,但处理数组必须用 NumPy。Q np.hypot 和手动算有什么区别?
A np.hypot(x, y) 等价于 sqrt(x squared + y squared),但内部做了溢出保护——先取绝对值再归一化计算,避免 x 或 y 很大时平方溢出。对极大值场景更安全,也支持多维度输入。
📖 小节
- 三角函数:
sin/cos/tan输入弧度,反三角arcsin/arccos/arctan/arctan2输出弧度 - 角度弧度转换:
np.radians角度→弧度,np.degrees弧度→角度 - 指数对数:
exp/log/log2/log10基本运算,log1p/expm1小值高精度 - 取整函数:
round银行家舍入,floor向 -∞,ceil向 +∞,trunc向零 - sign/abs/clip:
sign返回符号,abs绝对值,clip限幅,hypot欧氏距离
📝 作业
(1) 正弦/余弦函数
- 用
np.linspace生成 0~360° 的 361 个角度值 - 转为弧度后计算
sin和cos - 找出 sin 值最大的角度(用
np.argmax+np.degrees),验证是否为 90° - 计算 sin² + cos²,验证是否全为 1(用
np.allclose)
(2) exp/log 复利
- 年利率 5%,初始本金 10000 元,计算 1~30 年的复利终值:
10000 * np.exp(rate * years) - 计算每年终值的自然对数,验证 ln(FV) 是否为线性增长
- 用
np.log反算:从 30 年终值反推年利率,验证是否为 5%
(3) clip 信号限幅
- 生成 1000 个
rng.normal(0, 2)随机信号 - 用
np.clip将信号限制在 [-3, 3] - 统计被截断的样本数和占比
- 对比 clip 前后的均值和标准差变化
- 用
np.where实现与 clip 相同的效果,验证结果一致