# 数值微分 · 有限差分，以及它为什么不稳

> 用函数值近似导数，靠有限差分。但它藏着一个尴尬的矛盾：步长 h 越小，截断误差越小，可 h 太小又会引发灾难性抵消——存在一个最优 h，再小反而更差。这一章用「U 形误差曲线」把这个「天生不稳」讲透。

- 作者：David（道雾轩）
- 专栏：数值计算的艺术（https://daiw.org/manual/numerical-computing.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/numerical-computing/differentiation
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 数值微分 · 有限差分，以及它为什么不稳

第五站进入微积分的数值版。先看导数：很多时候我们只有 $f$ 的**函数值**（甚至 $f$ 是一段仿真代码，没有解析导数），却需要它的导数 $f'$。怎么办？用**有限差分**。但这一章的主角，是数值微分一个尴尬的性质——**它天生不稳**。

## 有限差分：用差商近似导数

导数的定义是 $f'(x)=\lim_{h\to0}\frac{f(x+h)-f(x)}{h}$。计算机取不了极限，就取一个**小而非零**的 $h$：

$$
f'(x) \approx \frac{f(x+h)-f(x)}{h} \quad(\text{前向差分,误差 } O(h))
$$

更准的是**中心差分**，用对称的两点，误差降一阶：

$$
f'(x) \approx \frac{f(x+h)-f(x-h)}{2h} \quad(\text{中心差分,误差 } O(h^2))
$$

由泰勒展开可证：前向差分的**截断误差**正比于 $h$，中心差分正比于 $h^2$。**所以 $h$ 越小，截断误差越小**——听起来该把 $h$ 取得越小越好？

## 陷阱：h 太小，抵消登场

**不能。** 这里 $h$ 越小越好的直觉，撞上了[灾难性抵消](https://daiw.org/manual/numerical-computing/cancellation)：

<Callout type="warn">
  **当 $h$ 很小时，$f(x+h)$ 和 $f(x)$ 几乎相等，它们的差 $f(x+h)-f(x)$ 正是「两个相近的数相减」——灾难性抵消！** 有效数字大量湮灭，而这个已经不准的差还要**除以一个极小的 $h$**（放大误差）。于是 $h$ 太小时，**抵消误差（舍入）反而主导**，结果越来越烂。

  两股误差此消彼长：$h$ 大 → **截断误差**大（近似太粗）；$h$ 小 → **抵消误差**大（舍入被放大）。中间存在一个**最优 $h$**，让两者之和最小——再小，误差不降反升。这就是数值微分「天生不稳」的根源：它**同时**踩了截断和抵消两个坑。
</Callout>

## 眼见为实：U 形误差曲线

用中心差分算 $f(x)=\sin x$ 在 $x=1$ 的导数（真值 $\cos 1$），扫一遍 $h$：

```python
import numpy as np

f = lambda x: np.sin(x)
true = np.cos(1.0)

for h in [1e-1, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-13]:
    approx = (f(1 + h) - f(1 - h)) / (2 * h)     # 中心差分
    print(f"h={h:.0e}   误差={abs(approx - true):.2e}")
```

```text
h=1e-01   误差=1.67e-03      ┐
h=1e-02   误差=1.67e-05      │ h 变小,截断误差主导 → 误差下降
h=1e-04   误差=1.67e-09      │
h=1e-06   误差=5.26e-11      ┘ 最优点附近(≈ 最小)
h=1e-08   误差=6.08e-09      ┐
h=1e-10   误差=2.94e-07      │ h 再小,抵消误差主导 → 误差回升!
h=1e-13   误差=7.36e-04      ┘
```

**误差先降后升，画出来是一条 U 形曲线。** 最优 $h$ 在 $10^{-6}$ 附近（中心差分约 $\epsilon^{1/3}$），此时误差最小；再往小取，舍入抵消占上风，越取越差。**「$h$ 取得越小导数越准」是彻头彻尾的错觉。**

## 解药：绕开减法

既然病根是「减法抵消」，自然想到[抵消那章](https://daiw.org/manual/numerical-computing/cancellation)的思路——**绕开减法**。有两个漂亮的办法：

- **复步长微分（complex-step）**：取虚步长，$f'(x)\approx \dfrac{\operatorname{Im}\big(f(x+ih)\big)}{h}$。它**根本不做减法**（信息藏在虚部），所以**没有抵消**，$h$ 可以取到 $10^{-100}$ 都不失精度！前提是 $f$ 能接受复数输入。
- **自动微分（automatic differentiation，AD）**：不做任何近似，靠链式法则**精确**算出导数，误差只有舍入级。这是现代深度学习框架（PyTorch、JAX）反向传播的底层——它让「求导」从「危险的数值近似」变成了「精确的机械计算」。

```python
# 复步长微分:无抵消,精度惊人
def complex_step_diff(f, x, h=1e-20):
    return (f(x + 1j*h)).imag / h

print(complex_step_diff(np.sin, 1.0))    # 0.5403023058681398 ≈ cos(1),几乎机器精度
```

## 小结与下一章

数值微分用有限差分（前向 $O(h)$、中心 $O(h^2)$），但它**天生不稳**：$h$ 大截断误差大、$h$ 小抵消误差大，存在**最优 $h$**，误差随 $h$ 呈 **U 形**——「越小越准」是错觉。解药是绕开减法：**复步长微分**（无抵消）或**自动微分**（精确）。

有趣的是，数值**积分**和微分恰好相反——它**又稳又好算**（积分是「求平均、抹平」，天然压制误差，不像微分放大误差）。下一章看数值积分，你会松一口气。👉 [数值积分：梯形、Simpson 与自适应](https://daiw.org/manual/numerical-computing/integration)。

## 参考文献

- [1] Nick Higham.《Accuracy and Stability of Numerical Algorithms》—— 有限差分的舍入误差。
- [2] Squire & Trapp.「Using complex variables to estimate derivatives.」1998. —— 复步长微分。
- [3] Baydin et al.「Automatic Differentiation in Machine Learning: a Survey.」2018. —— 自动微分综述。
