STM32浮点型变量数值大小边界判断有误的问题的原因及解决方案
STM32浮点型变量数值大小边界判断有误的问题的原因及解决方案
在使用STM32微控制器进行开发时,浮点型变量的边界判断问题可能会导致意想不到的结果。本文通过一个具体的案例,深入分析了这一问题的原因,并提供了实用的解决方案。
1. 问题描述
在使用STM32G431RBT6芯片的开发板时,需要实现一个按键控制浮点型变量累加与到达边界值清零的功能。具体代码如下:
float x = 0; // 浮点型变量初始化
/* 中间的其它代码与该问题无关,这里省略,只保留功能相关代码 */
if(ucKey_Down == 1) // 按下B1
{
if(x < 3.3)
{
x += 0.3;
}
else if(x >= 3.3)
{
x = 0;
}
}
然而,在使用LCD输出变量x的值时,发现了一个奇怪的现象:变量x竟然增加到了3.6后才清零。
2. 发现原因
通过调试功能,发现了问题的根源。当LCD显示x值为3.3时,x的实际值是3.29999971。对于程序中的判断而言,这个值仍然小于3.3,因此在下一次按下按键时,变量x会继续增加0.3,导致最终显示3.6的结果。
这种现象的原因在于浮点型变量的存储方式。浮点型变量与其他变量一样,都是按照二进制存储的。整数部分的数值存储可以实现十进制与二进制的完全无误差转化,但小数部分的存储精度是以二进制为基础的,最小计数单位是2的负几次方。因此,像0.3这样的数值无法精确表示为2的负幂指数之和的形式,在存储时就会产生误差。
例如,如果将浮点型变量x初始化为2.4,同样会在小数的最后几位出现误差。这种误差在正常计算和显示数值时可以通过四舍五入忽略,但在边界判断时可能会对程序产生决定性影响。
3. 检验猜测
为了验证上述猜测,可以尝试使用可以精确表示为2的负幂指数之和的数值。修改递增数值重新测试,修改后的代码如下:
float x = 0; // 浮点型变量初始化
/* 中间的其它代码与该问题无关,这里省略,只保留功能相关代码 */
if(ucKey_Down == 1) // 按下B1
{
if(x < 3.375)
{
x += 0.375;
}
else if(x >= 3.375)
{
x = 0;
}
}
由于0.375可以表示为2的负幂指数之和(0.375 = 2^{-1} + 2^{-2}),调试代码时发现当x加到最大值时得到的结果是x=3.375,且没有误差。这验证了之前的猜测是正确的。
4. 解决方法
为了避免存储误差带来的赋值细微误差造成的边界判断出错的问题,可以在边界判断时进行宽松处理。具体来说:
当判断条件中是"小于号"接边界值时,即if(x<bound),则对边界值在功能误差容许范围内进行细微减小处理。例如,将前面的判断3.3为x的上界的代码改写为:
float x = 0; // 浮点型变量初始化 /* 中间的其它代码与该问题无关,这里省略,只保留功能相关代码 */ if(ucKey_Down == 1) // 按下B1 { if(x < 3.29) { x += 0.3; } else if(x >= 3.29) { x = 0; } }
这样就可以实现在x理论值为3.3时,再次按下按键B1,x将回到0,实现了符合要求的循环递增。
如果判断的条件是大于边界值,那么就作增大判断边界值的宽松处理。例如:
float x = 3.0; // 浮点型变量初始化 /* 中间的其它代码与该问题无关,这里省略,只保留功能相关代码 */ if(ucKey_Down == 1) // 按下B1 { if(x >= 0.01) { x -= 0.3; } else if(x < 0.01) { x = 3.3; } }
这样就可以实现每次按下B1按键x递减0.3,直到减到理论值0时,x变为3.3,实现循环递减。