You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何向0.0应用指数移动平均(EMA)时收敛更慢?

指数移动平均收敛迭代次数差异的原因分析(与非正规数的关联)

先贴出你的测试代码:

#include <math.h>
#include <iostream>
using namespace std;
inline void Task(double start, double target) {
 double a0 = 0.0101252;
 double z = start;
 double value = -1.0;
 double temp = 0.0;
 int counter = 0;
 while (value != temp) {
 temp = value;
 // exponential moving average
 z += a0 * (target - z);
 value = z;
 counter++;
 }
 cout << "start: " << start << " | target: " << target << " | iterations: " << counter << std::endl;
}
int main() {
 Task(0.0, 0.01);
 Task(0.01, 0.0);
 Task(0.01, 0.02);
 Task(0.02, 0.01);
}

你的测试现象很典型:

指数移动平均(Exponential Moving Average)在非零值间切换或从0.0向非零值收敛时,迭代次数约3100次;但从0.01向0.0收敛时,迭代次数高达72305次,约为前者25倍。

这个差异确实和**非正规数(Denormals)**直接相关,咱们来拆解具体原因:

核心逻辑:非正规数导致的精度断崖式下降

先把EMA的更新公式变形一下:z = z + α*(target - z) 等价于 z = (1-α)*z + α*target。当目标值是0.0、起始值是0.01时,公式简化为 z = (1-α)*z,其中1-α ≈ 0.9898748。

  • 正规数阶段:当z还大于2^-1022(double类型的正规数最小值)时,每次乘以(1-α)都是按比例缩小,精度保持在double的15-17位有效范围内,收敛速度稳定。
  • 非正规数阶段:当z缩小到小于2^-1022后,就进入了非正规数区域。非正规数没有隐含的最高位1,靠尾数低位表示极小值,精度会随数值减小线性下降——最小的非正规double数只有1位有效精度。

进入这个区域后,z的更新不再是按比例缩放,而是变成了步进式缩小:每次只能减少一个当前精度下的最小单位,而不是乘以固定比例。这就导致收敛到0的过程变得异常缓慢,直到z最终下溢到0.0,才会触发value == temp退出循环。

其他场景为什么正常?

  • 从0.0向0.01收敛:z从0开始增长,很快就进入正规数范围(0.01远大于2^-1022),全程都是稳定的比例增长,迭代次数正常。
  • 非零值间切换:比如0.01→0.02或0.02→0.01,z始终处于正规数区间,更新过程的精度没有断崖式损耗,收敛速度保持一致。

验证小技巧

你可以在循环里加个判断,用std::fpclassify(z) == FP_SUBNORMAL检测z是否进入非正规数区域,会发现0.01→0.0的迭代后期,绝大多数次数都消耗在非正规数阶段。另外,开启编译器的**Flush to Zero(FTZ)**优化(比如GCC的-ffast-math),非正规数会被直接置为0,此时这个场景的迭代次数会立刻降到和其他场景一致,直接坐实非正规数的影响。

内容的提问来源于stack exchange,提问作者markzzz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:26:59