
1. 「到达目标」在阈值边界 oscillate
if (distance == 0.0) 在 SLAM 里几乎永真也不永假——轮速积分、IMU 预积分、代价收敛判据都要显式 epsilon。标定外参用四元数实部精确等于一几乎永 false;位姿比较无容差时控制器在阈值边界抖动,field 报「到不了点」。浮点不是「差不多就行」——判据写进测试才算数,否则现场调参只是在噪声阈值上作画,RViz 里看似到位、底盘仍在微动。
2. 比较:相对容差与角度 wrap
绝对容差在大数量级失效;相对容差更稳。四元数用角距离,别用点积判相似——q 与负 q 同旋转但点积接近负一会误判。球面插值路径选择也依赖符号统一。
bool near_equal(double a, double b, double rel = 1e-6) {
return std::abs(a - b) <= rel * std::max({1.0, std::abs(a), std::abs(b)});
}
double angle_diff(double a, double b) {
return std::remainder(a - b, 2 * M_PI);
}角度比较前归一化到 ,用 atan2(sin, cos) 比 while 循环更稳。陀螺积分里 yaw 漂移测试对 wrap 边界单独单测,别只在零附近测。团队新人仍常写双等号比较,要在 review 里当成硬规则拦。
3. 累积误差与大数加小数
航位推算关键状态用 double;float 跑两公里后 yaw 可漂零点四度——改 double 累积、float 仅发布显示。大数加小数:float 里程 x 已到千万级再加零点一可能不变;米级全局坐标别用 float 存世界系,传感器系内再用 float 省带宽。长轨迹 sum += dx 会 drift——关键统计用 Kahan 或 double 累加 float 增量。hypot 比自乘开方防 overflow。
4. 非结合律与确定性 replay
并行累加点云坐标,顺序不同结果微差——确定性回放要固定 reduction 顺序。同一 bag 两次 bit 一致要求禁用 fast-math、固定线程数;跨平台 golden 对比时关闭 fp 收缩。ARM 与 x86 融合乘加顺序不同,BA 残差范数末位不同但算法应一致。别混用 x87 八十位中间值与 sixty-four 位路径跑同一 golden test。
5. NaN 传播与日志
NaN 一旦进入状态向量,整条 EKF 链污染——比较前 isfinite guard。比较时间戳用整数纳秒更安全,别用 double 秒做长跑差分。gtest 用 EXPECT_NEAR 带 tolerance,注释物理意义(米、弧度)。日志打印残差范数用科学计数,别截断后误判收敛。EKF/BA 构建默认不开 fast-math,fast-math 会破坏 NaN 语义与 determinism。
6. 经典陷阱与 wheel odom
0.1 加 0.2 不等于 0.3 是入门经典,仍有人写精确相等。构造灾难性抵消:一亿加一再减一亿,naive 得零、补偿累加得 one。Recorded trajectory checksum 固定 reduction 顺序,跨平台 golden 一致。收敛判据 tolerance 写进测试注释,与物理单位对应。
7. 案例:到达判据无 epsilon 导致门区振荡
某导航节点用 pose 差分精确等于零判到达,仿真总能到点,field 里车在目标前十厘米来回蹭——控制器在阈值边界 oscillate。改成相对容差加角度 wrap 后同一任务稳定停稳。根因不是控制器增益,是浮点判据未写进契约。教训:所有「等于零」「等于目标」必须带 epsilon 与单测。复盘时要对照测试里是否写了物理单位(米、弧度),否则后人改阈值仍无据可依。
8. 验收
- 灾难性抵消用例 naive 失败、补偿通过。
- 角度 wrap 边界单独 gtest。
- 跨平台 golden 一致,fast-math 默认关。
- 到达/收敛判据 tolerance 与物理单位写在测试里。
- 四元数比较走角距离,禁止实部精确相等。
浮点判据是控制与优化的契约——未写进测试的 epsilon,会在 field 变成小阈值大事故;写进测试的 epsilon 才是可审计的行为接口。
相关
也可以看看
johan's blog