STM32浮点型变量数值大小边界判断有误的问题的原因及解决方案
STM32浮点型变量数值大小边界判断有误的问题的原因及解决方案
在使用STM32G431RBT6芯片进行开发时,遇到一个关于浮点型变量边界判断的问题。本文详细分析了问题的原因,并提供了具体的解决方案,对于从事嵌入式开发的工程师具有一定的参考价值。
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竟然增加到了3.6后才清零。
2. 发现原因
通过调试功能,发现了问题的根源。原来当LCD显示x值为3.3的时候,x的实际值是3.29999971。对于程序中的判断而言,这个值仍然比3.3小,因此下一次按下按键时,变量还会继续增加0.3,导致最终显示3.6的结果。
这种现象的原因在于浮点型变量的存储方式。浮点型变量与其他变量一样,都是按照二进制存储的。整数部分的数值存储可以实现十进制与二进制的完全无误差转化,但小数部分的存储精度是基于二进制的,最小计数单位是2的负几次方。因此,0.3这个数值在存储时本身就带有误差。同样的,如果浮点型变量的初始化值的小数部分不能表示成2的负整数幂次之和的形式,那么初始化值同样会在小数的最后几位出现误差,例如:
float x = 2.4;
这种误差在正常计算和显示数值时可以通过四舍五入忽略,但在边界判断时,这种微小的误差可能会对程序产生决定性的影响。
3. 检验猜测
考虑到浮点型变量的小数部分的存储数值为2的负整数幂指数的计算精度,如果小数部分可以表示为若干个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,实现循环递减。