NumPy: 数学函数

1. 你将学到


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. 原理图解

100%
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 很大时平方溢出。对极大值场景更安全,也支持多维度输入。

📖 小节


📝 作业

(1) 正弦/余弦函数

  1. np.linspace 生成 0~360° 的 361 个角度值
  2. 转为弧度后计算 sincos
  3. 找出 sin 值最大的角度(用 np.argmax + np.degrees),验证是否为 90°
  4. 计算 sin² + cos²,验证是否全为 1(用 np.allclose

(2) exp/log 复利

  1. 年利率 5%,初始本金 10000 元,计算 1~30 年的复利终值:10000 * np.exp(rate * years)
  2. 计算每年终值的自然对数,验证 ln(FV) 是否为线性增长
  3. np.log 反算:从 30 年终值反推年利率,验证是否为 5%

(3) clip 信号限幅

  1. 生成 1000 个 rng.normal(0, 2) 随机信号
  2. np.clip 将信号限制在 [-3, 3]
  3. 统计被截断的样本数和占比
  4. 对比 clip 前后的均值和标准差变化
  5. np.where 实现与 clip 相同的效果,验证结果一致
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏