docs(algo): 5.3 节补窗首下标的圆周图(三种写法对照)
- 新增配图 采样环缓冲_窗首下标.svg:左侧 40 格圆环走一遍 head = 5、n = 20 的「往回退 19 步」(跨过 0 与 39),右侧把直接减 / 16 位回绕后再取模 / 先加 DEPTH 三种写法的结果并列,只有第三种落在 25。 - 与 5.2 的环形布局图分工:那张讲「窗在哪几格」,这张讲「下标怎么算出来」, 不重复同一张图的语义。 - 生成器 gen_sample_ring_start_svg.py 顺带把 OVERFLOW 明细改到末尾统一打印 (原先只在开头打,折行类告警只报计数、看不出是哪一条)。 - 门禁:doc-links --sections-strict 通过;id-check 新增行无未登记编号; generated-fresh 22 个生成器 17 一致、0 新漂移;产物连跑两次 sha1 不变。
This commit is contained in:
@@ -205,15 +205,18 @@ flowchart TD
|
||||
代码里写成 `(s_head + SAMPLE_RING_DEPTH - n) % SAMPLE_RING_DEPTH` 而不是 `(s_head - n) % DEPTH`,
|
||||
是因为后者有两个独立的坑:
|
||||
|
||||
- **16 位无符号下溢后再取模不等价**:`s_head < n` 时减法先回绕成 `65536-(n-s_head)`,
|
||||
而 65536 不是 40 的倍数,`(x mod 65536) mod 40 ≠ x mod 40`。反例 `s_head = 3`、`n = 20`:
|
||||
正确窗首是 23,回绕后得 65519,`65519 % 40 = 39` → 整窗错位。
|
||||
- **有符号负取模**:两个 `uint16_t` 先提升成 `int`,`3 - 20 = -17`;C99 的 `%` 向零截断,
|
||||
`(-17) % 40 == -17`(不是 23),再转回无符号就是一次**越界读**——不报错、不 HardFault。
|
||||
- **16 位无符号下溢后再取模不等价**:`s_head < n` 时减法先回绕成 `65536-(n-s_head)` = 65521,
|
||||
而 65536 不是 40 的倍数,`(x mod 65536) mod 40 ≠ x mod 40`。同一个例子:正确窗首是 25,
|
||||
回绕后 `65521 % 40 = 1` → 整窗错位,读到的是环尾附近。
|
||||
- **有符号负取模**:两个 `uint16_t` 先提升成 `int`,`5 - 20 = -15`;C99 的 `%` 向零截断,
|
||||
`(-15) % 40 == -15`(不是 25),再转回无符号就是一次**越界读**——不报错、不 HardFault。
|
||||
|
||||
**先加一个 DEPTH** 同时解决两件事:`DEPTH ≡ 0 (mod DEPTH)`,加它不改变同余类、数学值不变;
|
||||
而 `s_head + DEPTH - n ∈ [0, 2·DEPTH - 2]`,恒非负、远小于 65535,两种整数提升规则下都不下溢。
|
||||
逐条反例与边界自检(`n == DEPTH` 时 `start == s_head`)见 `App/algo/sample_ring.c` 同名函数的注释。
|
||||
|
||||
边界自检:`n == DEPTH` 时 `start == s_head`——环满时「下一拍要覆盖的那格」就是最老点;
|
||||
未写满时 `s_head == s_filled`,故 `start >= 0`,窗一定不跨环尾。
|
||||
`App/algo/sample_ring.c` 里只留结论与指向本节的指针,推导以本节为准。
|
||||
|
||||
### 5.4 API 契约
|
||||
|
||||
|
||||
Reference in New Issue
Block a user