上一篇把连续信号从时域到采样的流程捋完,最后留了个尾巴——采样之后拿到的那串数字,究竟该怎么描述、怎么归类、怎么在纸面上写清楚。这篇就专门啃这块:离散信号的描述方式,以及那些必须背下来、不背就没法做题也没法做工程的典型序列。离散信号和典型序列这两个词,在数字信号处理、通信原理、自动控制这几门课里属于"地基级"概念,地基没打牢,后面讲卷积、Z变换、DFT 的时候你会发现每一步都在还债。我见过太多人卷积算得飞快,结果问一句"u(n) 和 δ(n) 差在哪"就卡住,这种基础概念的模糊会在后面反复收利息。
这篇内容我会按"描述方式 → 典型序列逐个拆 → 序列之间的运算关系 → 实操画图 → 踩坑记录"的顺序走。适合正在学信号与系统、数字信号处理的学生,也适合工作里偶尔要写点信号处理代码、但总觉得概念没串起来的工程师。看完你至少能做到两件事:拿到一个离散序列,能用三种不同的方式准确描述它;看到任意一个典型序列的表达式,能立刻说出它的波形长什么样、有什么性质、和别的序列怎么互相推导。
1. 先理清楚:离散信号、序列与数字信号的关系
1.1 为什么离散信号值得单独拎出来讲
很多人学完连续信号会下意识觉得,离散信号不就是把 t 换成 n 吗?公式照抄,图像照画,能有多难。这个想法在前两章还撑得住,到后面一定会崩。原因在于连续时间轴是稠密的,任意两个时刻之间还有无穷多个时刻;离散时间轴是离散的,n 和 n+1 之间什么都没有。这个差别看起来只是"点变稀疏了",实际后果是:连续信号里成立的很多微分、积分、周期性质,在离散域里要么改变形式,要么直接不成立。
举个最直观的:连续正弦 sin(ωt) 无论 ω 取什么值都是周期信号,但离散正弦 sin(ωn) 只有在 2π/ω 是有理数时才有周期。这个结论第一次见的人几乎都会愣一下。再比如积分运算,离散域的对应物是求和,而求和和积分在性质上有本质区别,这在后面推 Z 变换的收敛域时会体现得非常明显。所以离散信号必须当成一个独立对象重新建立体系,不能靠"连续那边改个符号"糊弄过去。
从工程角度讲,现实里进到处理器里的东西全是离散的。麦克风采到的音频、雷达收到回波、心电贴片读到的电压,最后都变成一串按时间先后排列的数字。描述这串数字的方式、它由哪些基本单元构成,直接决定了你后面怎么设计滤波器、怎么分析频谱。所以这一章不是纯理论铺垫,它是后面所有算法讨论的公共语言。
1.2 离散时间信号、序列、数字信号三者的边界
这三个词日常混用没人管你,但考试和写文档时混用是要扣分的,我建议从第一次接触就分清楚。
离散时间信号强调的是"时间上的离散"——只在离散时刻取值,但取值本身可以是任意实数甚至复数,不要求量化。比如 x(n) 在 n=3 时等于 3.14159...,这没问题,它仍然是离散时间信号。
序列是对离散信号的一种数学化称呼,本质是"以整数为自变量的函数",写成 {x(n)}。教材上经常把"序列"和"离散时间信号"当作同义词使用,严格来说序列更强调它是一个有序的数值排列,而离散时间信号更强调物理意义。我们在讨论数学性质时叫它序列,讨论物理系统时叫它信号,你按这个习惯走就不会别扭。
数字信号则是在离散时间信号的基础上又加了一层——幅度也量化了。量化意味着取值只能落在有限个电平上,会引入量化误差。所以链条是:连续信号 → 采样 → 离散时间信号 → 量化编码 → 数字信号。
注意:教材上一部分内容写"离散信号",其实默认指的是离散时间信号,没考虑量化。做题时如果题目没说量化位数,就按离散时间信号处理,别自己加戏去算量化误差。
理解了这三者边界,你再看"离散信号描述"这个标题就清楚了,它要解决的是:一个只在整数点上取值的序列,用什么方式写下来才能既不丢信息、又方便运算。
1.3 这篇内容适合谁看
如果你正在学信号与系统,这一章基本是必考内容,典型序列的性质、δ(n) 的抽样性质、正弦序列的周期判定,属于卷面上的常客,而且经常出在选择填空里,一步错步步错。如果你在做嵌入式或算法开发,需要写代码生成测试信号,那这里的内容就是你写测试用例时的工具箱,δ(n) 用来测冲激响应,u(n) 用来测阶跃响应,指数序列用来测系统的稳定性边界,这些都是实际调试时高频使用的。
还有一类读者是已经工作几年、概念有点模糊的工程师。这类人最典型的状态是"会用不会说",知道怎么调库函数生成一个单位脉冲,但说不清 δ(n) 的抽样性质为什么成立。这篇会把这层窗户纸捅破。不同的基础对应不同的读法:学生重点看性质和推导,工程师重点看描述方式和实操代码,复习的人可以直接跳到第 3 章的表格速查。
2. 离散信号的三种描述方式:怎么选、怎么用
2.1 解析式描述:闭式表达换来的通用性
解析式就是用数学表达式把序列写出来,比如 x(n) = a^n u(n)。这种写法的好处是一次性描述无穷多个点,而且能直接拿去做代数运算。你想对序列做移位,把 n 换成 n-2 就行;想做累加,直接上求和符号。凡是需要对序列做整体性推导的场合,解析式都是首选。
解析式描述的核心是"分段"和"门函数"两个技巧。绝大多数有用的序列都是单边的,只在 n ≥ 0 时有值,这时候就需要用 u(n) 来"开门"。比如一个从 n=0 开始按 0.8 的比率衰减的序列,写成 x(n) = 0.8^n u(n) 才严谨。
如果你偷懒只写 x(n) = 0.8^n,那么在 n = -1 时它会等于 1.25,在 n = -2 时等于 1.5625,序列会往左无限增长下去,和你要的物理意义完全对不上。这个错误我见过太多次,尤其是在手写答案的时候,省掉 u(n) 几乎成了默认操作,扣分扣得很冤枉。
提示:写解析式时,先问自己"这个序列在 n < 0 的时候有没有值"。只要答案是"没有",就必须挂上 u(n);如果是从某个正数点开始的,就用 u(n - n0)。
另一种情况是有限长序列,比如只在 0 ≤ n ≤ N-1 有值,这时候用矩形序列或者两个阶跃相减来卡边界,写成 x(n) = u(n) - u(n - N) 比写成分段函数更省事,也更容易做运算。
2.2 列表法:工程调试时最顺手的一种
列表法就是把序列值一个个列出来,通常写成 {x(n)} = {..., 0, 0, 1, 0.5, 0.25, 0.125, ...},然后用一个箭头或者下划线标出 n=0 的位置。这种写法看起来笨,但在工程上非常实用,因为它和实际拿到手的数组是一一对应的。
你从采集卡读出来一段数据,存进数组里就是列表法;你在纸上算两个短序列的卷积,所谓"翻褶、平移、相乘、求和",翻褶和相乘那两步用列表法最不容易错。特别是序列长度只有五六个点的时候,列表法比解析式快得多。
列表法的关键是那个"参考点"必须标清楚。序列 {1, 2, 3} 和 {1, 2, 3} 看起来一样,但如果一个以第一个元素为 n=0,另一个以第二个元素为 n=0,它们就是两个完全不同的序列。行业里的习惯是把 n=0 的位置用箭头标出来,写成 {1, 2, 3} 这种形式,或者干脆写成上下两行,上面一行是 n 的取值,下面一行是对应的序列值。
| n | -2 | -1 | 0 | 1 | 2 | 3 |
|---|---|---|---|---|---|---|
| x(n) | 0 | 0 | 1 | 0.5 | 0.25 | 0.125 |
这种表格形式我强烈建议在手工计算时使用,尤其是做卷积和移位的时候,把 n 那一行带着一起移动,出错概率会大幅下降。
2.3 图形法:stem 图和它的读图规则
图形法就是用波形图描述序列。这里有一个必须改过来的习惯:连续信号的图是连续曲线,离散信号的图是"茎叶图",每个整数点上画一根竖线,顶端有一个实心圆点。在 Python 里对应matplotlib.pyplot.stem(),在 MATLAB 里也是stem()。
为什么不能用 plot?因为 plot 会把相邻的离散点用直线连起来,视觉上暗示了这些点之间存在中间值,而离散信号在非整数点上根本没有定义。用 plot 画离散序列,在概念上就是错的,在展示时也容易误导人。
读图的时候还有几个约定要注意。横轴标的是 n,不是 t,单位是"序号",没有量纲。纵轴是序列值 x(n),可以是任意实数。如果序列是复数的,通常画两张图,一张实部一张虚部,或者一张幅度一张相位,不要试图在一张图上把复数画全。
还有一个容易忽略的点:单边序列画图时,很多人只画 n ≥ 0 的部分,这没错,但轴的起点最好稍微往左延伸一点,把 n < 0 的零值也标出来,让"单边"这个性质在图上可见。不然读者会以为序列从轴的左端就开始了。
2.4 三种描述方式的对照
三种方式没有优劣之分,只有适用场景的区别。我把它们放在一起对比如下。
| 描述方式 | 优势 | 局限 | 典型使用场景 |
|---|---|---|---|
| 解析式 | 可整体运算、可推导性质 | 不适合有限长、不规则序列 | 理论推导、性质证明、频域分析 |
| 列表法 | 与数组直接对应、手工计算不易错 | 无法表达无穷长序列 | 卷积计算、短序列分析、代码调试 |
| 图形法 | 直观、一眼看出趋势和对称性 | 精度低、无法精确读数 | 讲课展示、结果验证、报告配图 |
实际工作中这三种方式是混用的。我先用解析式定义序列,用代码生成数组(列表法的数字化形式),最后用 stem 图画出来检查结果是否符合预期。这个流程走顺了,基本不会再出现"算出来结果对但看不出对不对"的尴尬。
3. 典型离散信号逐个拆解(典型序列)
3.1 单位样值序列 δ(n):离散世界里的基本粒子
单位样值序列,也叫单位脉冲序列、单位冲激序列,定义非常干脆:n = 0 时等于 1,其余所有点等于 0。写成解析式就是 δ(n) = 1 (n = 0),0 (n ≠ 0)。注意它在 n=0 处取的是有限值 1,不是连续域里那个面积为一的冲激函数,这是离散和连续最容易混淆的第一个坑。
δ(n) 最重要的性质是抽样性质:x(n)·δ(n - n0) = x(n0)·δ(n - n0)。翻译成人话就是,任何序列乘以一个移位后的单位脉冲,只会保留那个位置上的值,其余全被清零。这个性质在整个信号处理里出现频率极高,卷积的定义、系统的冲激响应、Z 变换的推导全都要用到它。
比抽样性质更根本的是分解性质:任意序列都可以写成无数个移位加权的单位脉冲之和,即 x(n) = Σ x(k)δ(n - k),求和范围是所有整数。这个式子是离散信号分析的基石,它说明 δ(n) 是离散信号空间里的一组"基",就像三维空间里的 x、y、z 轴一样。理解了这一点,你就能理解为什么系统的冲激响应可以完全刻画一个线性时不变系统——因为任何输入都能拆成脉冲,而系统对脉冲的响应已知,叠加起来就是总响应。
δ(n) 还有一个容易被忽视的细节:它是一个能量信号,能量等于 1(因为只有一个点非零,模平方和就是 1),功率为零。这个结论在做能量功率分类题时经常用到。
3.2 单位阶跃序列 u(n):那个被漏写的门函数
单位阶跃序列定义为:n ≥ 0 时等于 1,n < 0 时等于 0。它的作用和连续域里的阶跃函数完全对应,就是给序列"开门"的。前面说解析式必须挂 u(n),挂的就是它。
u(n) 和 δ(n) 之间有两条非常漂亮的互推关系,必须记牢:
- 差分关系:δ(n) = u(n) - u(n - 1)。u(n) 在 n=0 处从 0 跳到 1,这个跳变幅度正好是 1,所以一阶差分就是单位脉冲。
- 累加关系:u(n) = Σ_{k=-∞}^{n} δ(k)。把从负无穷到 n 的所有单位脉冲加起来,只要 n ≥ 0,就至少包含了 k=0 那个脉冲,结果恒为 1;n < 0 时一个都没包含,结果为 0。
这两条关系是理解"累加"和"差分"这对逆运算的钥匙。在离散域里,累加对应连续域的积分,差分对应连续域的微分。所以 u(n) 是 δ(n) 的累加,δ(n) 是 u(n) 的差分,和连续域里阶跃是冲激的积分、冲激是阶跃的导数,结构完全一致。
注意:u(n) 是功率信号而不是能量信号。n → ∞ 时它的能量趋于无穷,但平均功率为 1/2。这个 1/2 的来历是对称定义下累加求和取平均的结果,算过一遍就记住了。
3.3 矩形序列 R_N(n):有限长的标准写法
矩形序列定义为:0 ≤ n ≤ N-1 时等于 1,其余等于 0。它是有限长序列的标准模板,长度为 N,起点在原点。
用阶跃表示就是 R_N(n) = u(n) - u(n - N)。这个写法的意义在于,你要描述任何有限长序列的"窗口",都可以用两个阶跃相减来卡边界。比如你想截取 x(n) 的前 8 个点,直接写 x(n)·R_8(n) 就完成了,不用写分段函数。
矩形序列在滤波器设计里出现得极其频繁——设计一个长度为 N 的 FIR 滤波器,本质就是拿一个理想滤波器的无限长冲激响应去乘一个矩形窗。而矩形窗的频谱是 sinc 形状,旁瓣衰减慢,会导致吉布斯现象。这个问题的根源就在 R_N(n) 的"硬截断"上,后面讲窗函数设计法时全部围绕它展开。
3.4 实指数序列与斜变序列:反映系统行为的两种基本形态
实指数序列写作 x(n) = a^n u(n),a 是实数。它的波形完全由 a 的取值决定,下面这张表建议直接记住。
| a 的取值 | 波形特征 | 典型含义 |
|---|---|---|
| a > 1 | 单调递增,越往后涨得越快 | 不稳定、发散 |
| a = 1 | 恒为 1(阶跃) | 临界状态 |
| 0 < a < 1 | 单调递减,趋于 0 | 稳定、收敛 |
| a = 0 | 只有 n=0 处为 1 | 退化为 δ(n) |
| -1 < a < 0 | 正负交替,幅度递减 | 稳定但振荡 |
| a = -1 | 正负交替,幅度不变 | 临界振荡 |
| a < -1 | 正负交替,幅度递增 | 不稳定振荡 |
这张表看起来是死记硬背,其实它对应的是离散系统的稳定性判据。一个一阶系统 y(n) = a·y(n-1) + x(n) 的冲激响应就是 a^n u(n),所以 |a| < 1 时系统稳定,|a| ≥ 1 时不稳定。后面讲 Z 变换时会发现,极点落在单位圆内对应稳定,这根线就是从这张表延伸出来的。
斜变序列定义为 x(n) = n·u(n),也就是 0, 1, 2, 3, ... 它相当于连续域的斜坡信号,主要用来测试系统对线性增长输入的跟踪能力。
提示:斜变序列不要和"n 从 0 开始"搞混。n·u(n) 在 n=0 时等于 0,所以它其实是从 0 开始线性增长的,第一个非零点在 n=1。如果题目要求从 1 开始递增到某个值,可能需要写成 (n+1)u(n),这个细节很容易在填空时错。
3.5 复指数序列:一个式子包含所有振荡和增长
复指数序列写作 x(n) = e^{(σ + jω0)n} = e^{σn}·(cos ω0 n + j sin ω0 n),也可以写成极坐标形式 A·α^n。它是离散信号里"最全"的一类,因为实指数、正弦、余弦全是它的特例。
复指数序列有两个参数起作用。σ 控制幅度包络,σ > 0 时包络指数增长,σ < 0 时指数衰减,σ = 0 时包络恒定。ω0 控制振荡频率,ω0 越大,单位样本之间相位变化越快,振荡越剧烈。
这里有一个离散域特有的现象:离散复指数的频率是周期的。因为 e^{j(ω0 + 2π)n} = e^{jω0 n}·e^{j2πn},而 e^{j2πn} = 1 对所有整数 n 恒成立,所以 ω0 和 ω0 + 2π 描述的是同一个序列。
更进一步,ω0 和 ω0 + 2πk(k 为任意整数)全都等价。这意味着离散复指数只需考虑 0 ≤ ω0 < 2π 的范围,超出这个范围的频率都是"别名"。这就是采样定理在频域的另一面表述:频率轴上的信息被折叠进了一个长度为 2π 的区间。连续的复指数 e^{jωt} 就没有这个性质,因为 t 是连续的,e^{j2πt} 不等于 1。
理解这一点,后面理解 DFT 的周期性、理解采样率为什么要大于两倍最高频率,都会顺很多。
3.6 正弦序列与它的周期判定
正弦序列写作 x(n) = A·sin(ω0 n + φ),是最常用的测试信号。它和连续正弦最大的区别就是周期性不再是必然的。
判定的方法很干脆:只要 2π/ω0 是有理数,正弦序列就是周期的;如果 2π/ω0 是无理数,它就不是周期的。等价的说法是 ω0/2π 为有理数。
当周期存在时,最小正整数周期 N 的求法是:把 ω0/2π 化成最简分数 M/N,那么这个 N 就是最小周期;或者说 N = 2πk/ω0,取使 N 为整数的最小正整数 k。
举几个例子,把过程走一遍:
- ω0 = π/6,则 2π/ω0 = 12,是有理数且已经是整数,所以 N = 12。
- ω0 = 4π/5,则 2π/ω0 = 5/2 = 2.5,是有理数。ω0/2π = 2/5,化成最简分数是 2/5,分母为 5,所以 N = 5。验证一下:n=0 和 n=5 时,相位差 4π/5 × 5 = 4π,是 2π 的整数倍,确实是周期的。
- ω0 = 1(弧度制),则 2π/ω0 = 2π 是无理数,非周期。
- ω0 = 3π/7,则 ω0/2π = 3/14,分母为 14,N = 14。
还有一种情况:ω0 = 0,此时序列是常数序列,可以认为它是周期的,任何正整数都是它的周期,通常说最小周期为 1。
注意:判定时一定要用弧度制,如果用角度制需要先换算。我见过有人把 ω0 = 60° 直接代入 2π/ω0 算出 6,虽然答案碰巧对(因为 60° 对应 π/3,2π/ω0 = 6),但推理过程是错的,换个角度就翻车了。
| ω0 取值 | 2π/ω0 | 是否周期 | 最小周期 N |
|---|---|---|---|
| π/6 | 12 | 是 | 12 |
| 4π/5 | 5/2 | 是 | 5 |
| 3π/7 | 14/3 | 是 | 14 |
| 1 | 2π | 否 | 不存在 |
| π | 2 | 是 | 2 |
| 2π/3 | 3 | 是 | 3 |
还有一个离散正弦的"反直觉"点:频率高低和视觉上振荡快慢的关系,在 ω0 接近 π 时会反转。ω0 从 0 增到 π,振荡越来越快;ω0 从 π 增到 2π,振荡反而越来越慢。这是因为 ω0 和 2π - ω0 是等价的(互为镜像),所以整个数字频率的有效振荡范围只有 0 到 π,π 对应的是最高频。这个结论对理解数字滤波器的截止频率上限为什么是 π(归一化后是 0.5)非常关键。
4. 序列的运算与相互关系:用 δ(n) 拼出整个世界
4.1 四种基本运算及其物理含义
序列的基本运算有四类,每一类都对应一个明确的物理操作,理解物理含义比记公式重要得多。
加法与乘法是最简单的,逐点进行。x(n) + y(n) 就是两个序列同一个 n 上的值相加。乘法的工程意义更大一些,两个序列相乘相当于用一个序列去"调制"或"加窗"另一个序列。比如你采集了一段无限长的数据,想只保留其中 1 秒的内容,就是乘以一个矩形序列,这就是加窗。
移位是把整个序列沿 n 轴平移。x(n - m) 表示原序列向右移 m 个位置,m > 0 时是延迟,m < 0 时是超前。这个方向和直觉是反的——括号里减,图上往右走。第一次学的人都觉得别扭,但只要记住"需要 x 在更早的 n 上取值,才能让输出在更大的 n 上非零",逻辑上就通了。
反褶是把序列关于 n = 0 翻转,x(-n)。反褶在卷积里是核心步骤,因为卷积的定义 x(n) * h(n) = Σ x(k)h(n - k) 里,h 是先反褶再移位的。做卷积时顺序不能乱:先翻、再移、再乘、再求和。
累加与差分是互逆运算,前面讲 u(n) 和 δ(n) 关系时已经出现过。差分操作 y(n) = x(n) - x(n - 1) 相当于离散域的高通滤波,它放大快速变化、抑制缓慢变化,这个直觉在做系统辨识时很有用。
4.2 从 δ(n) 出发推导其他序列
把所有典型序列放在一起看,会发现它们之间是一棵清晰的"关系树",掌握这层关系,记忆量能减少一半。
- δ(n) → u(n):累加。u(n) = Σ δ(k),k 从 -∞ 到 n。
- u(n) → δ(n):差分。δ(n) = u(n) - u(n - 1)。
- u(n) → R_N(n):相减。R_N(n) = u(n) - u(n - N)。
- δ(n) → 一般序列:加权叠加。x(n) = Σ x(k)δ(n - k)。
- δ(n) → 实指数:a^n u(n) 在 n=0 处退化就是 δ(n)(当 a 无关紧要时)。
- 复指数 → 正弦:取虚部得到 sin,取实部得到 cos。
这棵树的意义在于,任何复杂的序列运算最终都可以拆解到 δ(n) 上。你在做卷积、做 Z 变换、做系统分析时,本质上都是在做"用脉冲表示一切"这件事。所以我一直建议初学的人把 δ(n) 的分解性质反复推导几遍,推到不用想就能写出来的程度。
4.3 尺度变换:抽取与内插容易踩的坑
离散域的尺度变换和连续域区别很大,是另一个高频出错点。连续信号做 x(at),a > 1 是压缩,波形变窄。离散信号做 x(an),当 a 是大于 1 的整数时,只有 n 是 a 的倍数的那些点被保留下来,其余全部丢弃,这叫抽取。
抽取的问题在于它会丢失信息。你有一个 ω0 = π/10 的低频正弦,做 2 倍抽取后变成 sin(2ω0 n) = sin(πn/5),频率变成了原来的 2 倍,看起来还正常。但如果原来是 ω0 = 4π/5,抽取后变成 8π/5,而 8π/5 等价于 8π/5 - 2π = -2π/5,频率反而降低且符号翻转了,出现了混叠。这就是为什么实际做抽取之前必须先做抗混叠滤波,不能直接丢点。
反过来,把 x(n) 变成 x(n/a) 叫内插,需要在原来的点之间插入零值,然后做低通滤波把零值填上。只插零不滤波,频谱会出现镜像,波形也会变得锯齿状。这两步缺一不可。
提示:考试里遇到 x(2n) 这种表达,先写出原序列的 n 取值列表,然后只保留 n 为偶数的点,再重新编号,比直接套公式稳妥得多。
4.4 Python 实操:把所有典型序列画出来并验证周期
光看公式容易忘,动手画一遍印象完全不一样。下面这段代码把这一章讲的主要序列都生成并画出来了,可以直接跑。
import numpy as np import matplotlib.pyplot as plt from fractions import Fraction # 定义 n 的范围 n = np.arange(-6, 31) # 单位样值序列 delta = np.where(n == 0, 1.0, 0.0) # 单位阶跃序列 u = np.where(n >= 0, 1.0, 0.0) # 矩形序列 R_8(n) N_rect = 8 rect = np.where((n >= 0) & (n <= N_rect - 1), 1.0, 0.0) # 实指数序列 0.75^n u(n) a = 0.75 exp_seq = np.where(n >= 0, a ** n, 0.0) # 交替序列 (-0.8)^n u(n) b = -0.8 alt_seq = np.where(n >= 0, b ** n, 0.0) # 正弦序列,omega0 = pi/6,最小周期应为 12 w0 = np.pi / 6 sine_seq = np.sin(w0 * n) fig, axes = plt.subplots(3, 2, figsize=(12, 10)) seqs = [delta, u, rect, exp_seq, alt_seq, sine_seq] titles = ['delta(n)', 'u(n)', 'R_8(n)', '0.75^n u(n)', '(-0.8)^n u(n)', 'sin(pi*n/6)'] for ax, s, t in zip(axes.ravel(), seqs, titles): ax.stem(n, s, basefmt=' ') ax.set_title(t) ax.grid(alpha=0.3) plt.tight_layout() plt.show()跑完之后重点看两张图。一张是(-0.8)^n u(n),你会看到正负交替、幅度递减的形态,这和前面那张表的描述完全吻合。另一张是sin(pi*n/6),你能肉眼数出它每 12 个点重复一次,验证了周期判定的结果。
再补一段周期判定的代码,把判断逻辑自动化,遇到不确定的频率直接算:
def check_periodicity(w0, max_den=1000): """ 判断离散正弦 sin(w0*n) 是否周期,并返回最小周期。 w0 单位是弧度。 """ ratio = w0 / (2 * np.pi) frac = Fraction(ratio).limit_denominator(max_den) # 判断近似是否足够精确 if abs(frac - ratio) > 1e-9: return False, None return True, frac.denominator # 测试几个频率 for w in [np.pi/6, 4*np.pi/5, 3*np.pi/7, 1.0, np.pi, 2*np.pi/3]: periodic, period = check_periodicity(w) print(f"w0 = {w:.6f}\t周期: {periodic}\t最小周期: {period}")Fraction的limit_denominator方法很关键,因为浮点数没办法精确表示 π,直接用 Fraction 会得到一个巨大的分数,必须限制分母上限。这里设成 1000,意思是"如果能用一个分母不超过 1000 的分数近似到 1e-9 以内,就认为是有理数"。对于教材上给出的频率,这个精度足够。
5. 常见问题与排查技巧实录
5.1 周期判定总出错?先检查这三个地方
周期判定出错的原因归纳起来无非三种。第一种是单位没换算,题目给的是角度,你直接代入了公式,或者反过来。第二种是没化简到最简分数,比如算出 ω0/2π = 4/6,直接取分母 6 作为周期,其实最简是 2/3,最小周期应该是 3。第三种是把 2π/ω0 和 ω0/2π 弄反了,明明答案是 14,你算出个 3/14 之类的东西。
我自己的习惯是固定一个流程:先算 ω0/2π,化成最简分数 M/N,然后 N 就是最小周期。如果 ω0/2π 化成的是整数 k,那最小周期就是 1,意思是这个序列所有点都相同(比如 ω0 = 2π 时)。这个流程走三遍就形成肌肉记忆了,考试不会再错。
| 常见错误 | 错误表现 | 正确做法 |
|---|---|---|
| 单位未换算 | 代入角度值得出荒谬结果 | 统一换成弧度制 |
| 未化最简分数 | 周期取到分母的倍数 | 约分到最简,取分母 |
| 分子分母颠倒 | 得到小于 1 的周期 | 用 ω0/2π,不是 2π/ω0 |
| 忽略无理数情况 | 强行算出一个整数周期 | 2π/ω0 为无理数则非周期 |
5.2 绘图与数值计算的坑
画图最容易犯的错是用 plot 而不是 stem。这个前面说过,但实际写代码时手一抖还是会用错。建议在画离散序列时养成固定写法,直接敲ax.stem(),别给自己犯错的机会。
数值上最坑的是指数序列的溢出和下溢。计算0.75 ** n时如果 n 很大,值会非常接近 0,在双精度下到 n 大约 4000 多就下溢成 0 了。如果计算1.2 ** n,n 到 3000 左右就会溢出成 inf。所以在做长序列仿真时,要么限制 n 的范围,要么用对数域计算,要么做归一化。这个问题在仿真递归滤波器时会突然冒出来,让人摸不着头脑,提前知道能省很多调试时间。
另一个坑是np.where的求值顺序。np.where(n >= 0, a ** n, 0.0)这行代码里,a ** n会对所有 n 求值,包括负数 n。当 a = 0 时,0 ** (-1)会触发除零警告。虽然最终结果被 where 替换成了 0,但警告还是会刷屏。解决办法是先用掩码取子集,或者用np.power配合where参数的写法。
提示:如果序列里有负底数的幂运算,注意 Python 中
(-0.8) ** n对整数 n 是合法的,但如果 n 是浮点数组,负数底数的非整数次幂会返回 NaN。所以生成 n 时用np.arange得到整数数组,别用np.linspace。
5.3 概念混淆的高频清单
最后列几个我被问得最多的问题,都是概念层面的混淆。
第一,δ(n) 和连续冲激函数 δ(t) 混为一谈。δ(t) 是一个广义函数,取值在 t=0 处"无穷大",但积分为 1,它是一个极限意义下的对象。δ(n) 就是一个普通的序列,n=0 时等于 1,没有任何无穷大。拿面积去理解 δ(n) 会越想越乱,直接按定义记就好。
第二,u(n) 和 u(t) 的不连续点问题。u(t) 在 t=0 处有定义争议(有的教材定义成 0.5),u(n) 没有这个问题,n=0 明确等于 1。
第三,周期性和频率的关系。离散正弦的频率 ω0 和 ω0 + 2π 等价,所以"频率范围"只有 2π 宽。而连续性里频率是无限延伸的,两者不能类比。这个区别在理解采样定理和频谱混叠时是关键。
第四,能量信号和功率信号。判断方法是先算能量 E = Σ|x(n)|²,如果 E 有限则是能量信号;如果 E 无限但平均功率有限,则是功率信号;两者都无限则是既非能量也非功率信号,比如 n·u(n)。δ(n) 是能量信号,u(n) 是功率信号,a^n u(n) 在 |a| < 1 时是能量信号、|a| = 1 时是功率信号、|a| > 1 时两者皆非。这套分类和连续域完全平行,记住对应关系就行。
按我自己的经验,把这几条理顺之后,离散信号这一块基本就算通了。你后面再去碰卷积、Z 变换、DFT,会发现每一步都能回溯到这章讲的某个序列或某个性质上——卷积的本质是脉冲分解,Z 变换的本质是复指数序列的叠加,DFT 的周期性本质是离散频率的周期性。根基打得越实,后面学得越快,这是我在带人做项目时最深的体会。