5.3 函数的极限、连续与初等函数
上一节讨论数列的极限,自变量只取离散的下标。这一节把极限搬到函数上:当
连续性有三个层次,区别在于「误差控制是否与位置有关」。逐点连续在每个点单独给误差标准;一致连续用同一个误差标准管住整个区间;Lipschitz 连续进一步要求误差与自变量变化成正比。三个层次的关系是包含的:Lipschitz 蕴含一致连续,一致连续蕴含逐点连续,反之都不成立。区分它们的原因在于实际含义不同:逐点连续只保证「函数图像不断开」,一致连续保证「用有限精度的输入得到有限精度的输出」(数值计算的稳定性),Lipschitz 连续还给出误差的定量上界(用于收敛速度估计)。
本节最后一段内容处理连续性的定量刻画与数值实现。连续模
5.3.1 函数极限
定义与语言
设函数
记作
单侧极限把
无穷远与无穷大:
Heine 归结原则:
运算法则与夹逼:两个函数极限的四则运算与数列版本相同;夹逼定理也相同:若在
5.3.2 连续与间断点
连续的定义
称
三种说法等价:
间断点按极限的行为分类。可去间断点:左右极限存在且相等,但函数值不等于极限值或函数在该点无定义(如
连续函数的运算:连续函数的和、差、积、商(分母不为零处)仍连续;连续函数的复合仍连续。初等函数(幂函数、指数函数、对数函数、三角函数、反三角函数及其复合)在定义域内连续,这一条使大多数函数的连续性检查可以省略,也让求极限时可以直接代入(这是「直接代入」的合法性来源)。
5.3.3 闭区间上连续函数的性质
闭区间上的连续函数满足三条基本定理,它们的证明都依赖紧性(有界闭集)与连通性(区间)。
最值定理:闭区间
介值定理:闭区间上的连续函数取到两个函数值之间的任何值。若
反函数连续性:闭区间上严格单调的连续函数的反函数连续。单调性提供一一对应,紧性保证反函数的连续性。
一致连续(Cantor 定理):闭区间上的连续函数一致连续。这条结论把逐点连续升级为整体一致,证明依赖有限覆盖定理,也是「紧集上的连续函数一致连续」的特例。
5.3.4 连续模、Lipschitz 与 Hölder 条件
一致连续的定义
与逐点连续的差别在
判定要点:在闭区间上,连续与一致连续等价(Cantor 定理);在开区间或无穷区间上,一致连续需要额外条件,常见的充分条件是导数有界(Lipschitz 条件)。
连续模
把一致连续的量写成函数,得到连续模:
既不 Lipschitz 也不 Hölder 的一致连续函数存在,它们的连续模可以更慢(例如
不连续函数的连续模不趋于零:阶跃函数(
为什么要区分这三种条件
三个条件对应三种保证。逐点连续保证函数值不跳变,是理论分析的最低要求。一致连续保证「输入误差小则输出误差小」这件事在整个区域一致成立,这是数值计算的基本前提:只有一致连续的函数才能用有限精度输入得到有限精度输出,误差不随位置恶化。Lipschitz 条件给出误差的定量上界,用于分析算法的收敛速度:压缩映射原理的压缩常数就是 Lipschitz 常数,梯度下降的步长上界由梯度的 Lipschitz 常数决定。连续模是这三者的统一语言:
5.3.5 数值实验
实验一:极限计算中的数值陷阱
三个常见极限都可以解析求出,值分别是
import math
xs = [1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-16]
print("(1 - cos x)/x^2 的两种算法(极限为 0.5):")
print(f"{'x':>8} {'直接计算':>22} {'相对误差':>12} {'稳定算法':>22} {'相对误差':>12}")
for x in xs:
direct = (1.0 - math.cos(x)) / (x * x)
stable = 2.0 * math.sin(x / 2.0) ** 2 / (x * x)
print(f"{x:>8.0e} {direct:>22.6e} {abs(direct - 0.5) / 0.5:>12.3e} "
f"{stable:>22.6e} {abs(stable - 0.5) / 0.5:>12.3e}")
print("(e^x - 1)/x 的两种算法(极限为 1):")
for x in [1e-8, 1e-12, 1e-16]:
direct = (math.exp(x) - 1.0) / x
stable = math.expm1(x) / x
print(f" x = {x:.0e}: 直接计算 = {direct:.6e}(误差 {abs(direct - 1):.3e}), "
f"expm1 = {stable:.6e}(误差 {abs(stable - 1):.3e})")
print("(sqrt(1+x) - 1)/x 的两种算法(极限为 0.5):")
for x in [1e-8, 1e-12, 1e-16]:
direct = (math.sqrt(1.0 + x) - 1.0) / x
stable = 1.0 / (math.sqrt(1.0 + x) + 1.0)
print(f" x = {x:.0e}: 直接计算 = {direct:.6e}(误差 {abs(direct - 0.5):.3e}), "
f"共轭化简 = {stable:.6e}(误差 {abs(stable - 0.5):.3e})")输出:
(1 - cos x)/x^2 的两种算法(极限为 0.5):
x 直接计算 相对误差 稳定算法 相对误差
1e-02 4.999958e-01 8.333e-06 4.999958e-01 8.333e-06
1e-04 5.000000e-01 6.077e-09 5.000000e-01 8.333e-10
1e-06 5.000445e-01 8.890e-05 5.000000e-01 8.338e-14
1e-08 0.000000e+00 1.000e+00 5.000000e-01 0.000e+00
1e-10 0.000000e+00 1.000e+00 5.000000e-01 0.000e+00
1e-16 0.000000e+00 1.000e+00 5.000000e-01 0.000e+00
(e^x - 1)/x 的两种算法(极限为 1):
x = 1e-08: 直接计算 = 1.000000e+00(误差 6.077e-09), expm1 = 1.000000e+00(误差 5.000e-09)
x = 1e-12: 直接计算 = 1.000089e+00(误差 8.890e-05), expm1 = 1.000000e+00(误差 5.000e-13)
x = 1e-16: 直接计算 = 0.000000e+00(误差 1.000e+00), expm1 = 1.000000e+00(误差 0.000e+00)
(sqrt(1+x) - 1)/x 的两种算法(极限为 0.5):
x = 1e-08: 直接计算 = 5.000000e-01(误差 3.039e-09), 共轭化简 = 5.000000e-01(误差 1.250e-09)
x = 1e-12: 直接计算 = 5.000445e-01(误差 4.445e-05), 共轭化简 = 5.000000e-01(误差 1.250e-13)
x = 1e-16: 直接计算 = 0.000000e+00(误差 5.000e-01), 共轭化简 = 5.000000e-01(误差 0.000e+00)三组数据的形态一致。expm1(直接计算
同一类陷阱在统计学与优化里也常见:方差计算中的 log1p、-expm1)。
实验二:一致连续的数值判定
一致连续的直接检验是观察「跨度固定为
import math
import numpy as np
def sup_diff(f, lo, hi, h, n=40000):
x = np.linspace(lo, hi - h, n)
return float(np.max(np.abs(f(x + h) - f(x))))
print("f(x) = x^2 在区间上的最大斜率(近似 Lipschitz 常数):")
for b in [1.0, 10.0]:
x = np.linspace(0, b, 100001)
slope = np.max(np.abs((x[1:] ** 2 - x[:-1] ** 2) / (x[1:] - x[:-1])))
print(f" [0, {b:.0f}]: 最大斜率 = {slope:.6f}(理论值 2b = {2 * b:.1f})")
print("f(x) = sqrt(x) 在 (0, 1] 上的最大斜率(随区间缩小):")
for lo in [1e-2, 1e-4, 1e-6]:
x = np.linspace(lo, 1.0, 200001)
slope = np.max(np.abs((np.sqrt(x[1:]) - np.sqrt(x[:-1])) / (x[1:] - x[:-1])))
print(f" 从 {lo:.0e} 起的最大斜率 = {slope:.3e}(理论值 1/(2 sqrt(lo)) = "
f"{1 / (2 * math.sqrt(lo)):.1e})")
print("一致连续的判别:sup |f(x+h) - f(x)| 随 h 的变化")
print(f"{'h':>10} {'sqrt(x) 在 [0,1]':>22} {'sin(1/x) 在 [0.001,1]':>26}")
for h in [1e-2, 1e-3, 1e-4, 1e-5]:
a = sup_diff(np.sqrt, 0.0, 1.0, h)
b = sup_diff(lambda t: np.sin(1.0 / t), 1e-3, 1.0, h)
print(f"{h:>10.0e} {a:>22.6e} {b:>26.6e}")输出:
f(x) = x^2 在区间上的最大斜率(近似 Lipschitz 常数):
[0, 1]: 最大斜率 = 1.999990(理论值 2b = 2.0)
[0, 10]: 最大斜率 = 19.999900(理论值 2b = 20.0)
f(x) = sqrt(x) 在 (0, 1] 上的最大斜率(随区间缩小):
从 1e-02 起的最大斜率 = 4.999e+00(理论值 1/(2 sqrt(lo)) = 5.0e+00)
从 1e-04 起的最大斜率 = 4.939e+01(理论值 1/(2 sqrt(lo)) = 5.0e+01)
从 1e-06 起的最大斜率 = 2.899e+02(理论值 1/(2 sqrt(lo)) = 5.0e+02)
一致连续的判别:sup |f(x+h) - f(x)| 随 h 的变化
h sqrt(x) 在 [0,1] sin(1/x) 在 [0.001,1]
1e-02 1.000000e-01 1.989633e+00
1e-03 3.162278e-02 1.998970e+00
1e-04 1.000000e-02 1.986319e+00
1e-05 3.162278e-03 1.990829e+00三组数据对应三个结论。
Lipschitz 常数的估计。
区间位置对 Lipschitz 性的影响。
一致连续的判定数据。
实验三:连续模的估计
连续模把「误差控制」写成关于跨度的函数。对三类函数计算
import math
import numpy as np
def modulus(f, h, lo=0.0, hi=1.0, n=200000):
x = np.linspace(lo, hi - h, n)
return float(np.max(np.abs(f(x + h) - f(x))))
funcs = [("x^2", lambda t: t ** 2, 0.0, 1.0),
("sqrt(x)", np.sqrt, 0.0, 1.0),
("阶跃(x > 0 取 1)", lambda t: np.where(t > 0, 1.0, 0.0), -0.5, 0.5)]
for name, f, lo, hi in funcs:
print(f"{name} 在 [{lo}, {hi}] 上的连续模:")
prev = None
for h in [1e-1, 1e-2, 1e-3, 1e-4, 1e-5]:
w = modulus(f, h, lo, hi)
if prev is None:
slope_txt = ""
else:
slope_txt = f", 局部斜率 = {math.log(w / prev[1]) / math.log(h / prev[0]):.3f}"
print(f" delta = {h:.0e}: omega = {w:.6e}{slope_txt}")
prev = (h, w)输出:
x^2 在 [0.0, 1.0] 上的连续模:
delta = 1e-01: omega = 1.900000e-01
delta = 1e-02: omega = 1.990000e-02, 局部斜率 = 0.980
delta = 1e-03: omega = 1.999000e-03, 局部斜率 = 0.998
delta = 1e-04: omega = 1.999900e-04, 局部斜率 = 1.000
delta = 1e-05: omega = 1.999990e-05, 局部斜率 = 1.000
sqrt(x) 在 [0.0, 1.0] 上的连续模:
delta = 1e-01: omega = 3.162278e-01
delta = 1e-02: omega = 1.000000e-01, 局部斜率 = 0.500
delta = 1e-03: omega = 3.162278e-02, 局部斜率 = 0.500
delta = 1e-04: omega = 1.000000e-02, 局部斜率 = 0.500
delta = 1e-05: omega = 3.162278e-03, 局部斜率 = 0.500
阶跃(x > 0 取 1) 在 [-0.5, 0.5] 上的连续模:
delta = 1e-01: omega = 1.000000e+00
delta = 1e-02: omega = 1.000000e+00, 局部斜率 = -0.000
delta = 1e-03: omega = 1.000000e+00, 局部斜率 = -0.000
delta = 1e-04: omega = 1.000000e+00, 局部斜率 = -0.000
delta = 1e-05: omega = 1.000000e+00, 局部斜率 = -0.000三类函数的局部斜率分别稳定在
5.3.6 本节小结
从极限到连续的三级阶梯
本节把数列极限推广到函数极限,再引出连续性的三层结构。函数极限用 ε-δ 定义,Heine 归结原则把它与数列极限连通,两侧的运算法则与夹逼定理与数列版本一致。连续要求极限值等于函数值,间断点按单侧极限的存在与相等情况分成可去、跳跃、第二类三种。闭区间上的连续函数有三条基本结论:最值定理(取到最大最小值)、介值定理(取到中间值,零点定理是特例,也是数值求根的基础)、一致连续(Cantor 定理)。一致连续与逐点连续的差别在于误差标准是否与位置有关,闭区间上两者等价,开区间或无穷区间上需要额外条件。连续模
连续函数最重要的一条性质是「局部可以用直线近似」,这就是下一节的微分学:导数把函数在一点附近的行为压缩成一个数,中值定理与 Taylor 公式把这个数推广到整个区间的估计,而本节的一致连续与 Lipschitz 条件正是微分学结论(导数有界)的连续语言表述。
练习题
第 1 题 概念推导
(a) 用 ε-δ 定义证明
参考答案
(a) 任给
(b) 由归结原则,只需构造两个趋于零的数列使函数值趋于不同的极限。取
(c) 设
反例:
第 2 题 计算推理
(a) 求
参考答案
(a) 第一式用倍角公式
第二式提出
两式都用到了基本极限
(b)
(c) 对 np.cbrt 或 lambda t: np.sign(t) * np.abs(t) ** (1/3))。
第 3 题 代码验证
用本节代码做三组实验并解释:(a) 把实验一中
参考答案
(a) mpmath 一类)在小范围内对照。
(b) 定义域对判定影响明显。取
(c)
第 4 题 综合应用
某数值程序需要计算函数
参考答案
改写:直接写 (np.exp(t) - 1 - t) / t**2 在 (np.expm1(t) - t) / t**2(expm1 处理第一次抵消);
当
验证:三条手段。其一,与高精度计算对照:用 mpmath 或 decimal 模块在
极小 log1p、expm1 计算
与一致连续的联系:这个问题是「连续模与数值稳定性」的具体形态。
常见错误
错误 1 · 用「代入特殊点或数列」代替极限的存在性证明
原因:计算极限时用几个点或几个数列的数值结果代替证明,会漏掉极限不存在的情形。
解决:用 Heine 归结原则时说明「对任意数列都成立」,或者用 ε-δ 定义给出
错误 2 · 混用逐点连续与一致连续
原因:两者的差别不显眼,但在数值与算法分析中影响很大。
解决:记住判定表:闭区间上连续等价于一致连续;开区间或无穷区间上需要额外条件(导数有界、连续模趋于零、极限存在且有限)。写误差估计时明确使用的条件与所在区间,不要把在一个区间上成立的结论跨区间使用。
错误 3 · 在数值代码中直接写有抵消的表达式
原因:
解决:识别「两个接近的数相减」的结构并改写:expm1;log1p;logaddexp。改写之后用小量级输入做回归测试(
错误 4 · 把间断点分类与可去性判断混为一谈
原因:左右极限存在且相等(可去间断点)与函数在该点连续是两个不同的判断。
解决:判断连续性按三步走:函数在该点是否有定义;左右极限是否存在且相等;函数值是否等于极限值。三步都满足才连续。分类时先看单侧极限是否存在(区分第一类与第二类),再比较两侧是否相等(区分可去与跳跃)。