浮点数比较陷阱

前提:

  • 我维护的老代码, 没有文件系统, 持久性变量被存在了 flash 的固定位置
  • 一个结构体里面存了好几个浮点数, float 型
  • 读出该结构体之后会对结构体进行数据矫正, 将异常值修改成默认值
  • 在对 MCU flash 全片擦除, 烧录之后运行会导致死机, 定位原因发现有个 float 类型的 field 不会被正确矫正, 导致调用字库的时候溢出, 进入 hardfault 中断从而出现死机.

分析:

问题对比

if (sys_parms.fm_now_fre < 87.5 || sys_parms.fm_now_fre > 108.5) {
	sys_parms.fm_now_fre = 87.5;
}

这个 field fm_now_fre 被成功矫正成了 87.5

if (sys_parms.sys_vol > 60.0 || sys_parms.sys_vol < 0.0) {
	sys_parms.sys_vol = 20.0;
}

这个filed sys_vol 没有被成功矫正, 经过调试器观察为 -nan

原因

  • flash 在被擦除之后是全0xff, 所以 float 类型的值就是 -nan
  • 编译器优化了浮点数的比较过程, 在比较非零正数的时候会把数值转换成一个整型, 但是在对比 0 的时候因为零的浮点数规则特殊是使用浮点运算器运算的, 不是直接由cpu计算, 浮点数和 nan 比较的时候就是 false 所以这个 sys_vol 就没有得到矫正, 导致了数组溢出的问题.

汇编展示

float 值和非零正数比较汇编

0x08012B7E LDR  r1, [r10, #0x00]   ; 加载 FM_Now_Fre 的原始位模式 (0xFFFFFFFF)
0x08012B82 ADDS r2, r1, r0         ; 整数加法!(r0 是一个偏移量常量)
0x08012B84 MOV  r1, #0x2A0000      ; 设定一个整数阈值
0x08012B88 CMP  r2, r1             ; 整数比较!
0x08012B8A BLS  0x08012B94         ; 如果小于等于则跳转(跳过修正逻辑)

float 值和零比较汇编

0x08012D0C EEB50AC0  VCMPE.F32  s0,#0.0         ; 真正的 FPU 比较指令!判断是否 < 0.0
0x08012D10 EEF1FA10  VMRS       APSR_nzcv,FPSCR ; 关键:把 FPU 的状态位搬到 CPU 状态寄存器
0x08012D14 D201      BCS        0x08012D1A      ; 如果 Carry 位被置 1,跳转到 0x08012D1A (跳过赋值)
posted @ 2026-03-20 16:16  LogicBai  阅读(23)  评论(0)    收藏  举报