目的是在OLED 屏幕上显示浮点数,有几个设计要求:
- 我已经有一个现成的能显示整数的函数,希望尽量复用;
- 尽量不使用除法;
- 不需要考虑小数四舍五入的问题;
我觉得小数四舍五入其实很多时候没什么用处,1.999 显示成1.99 或者2.00,没什么差别,两个结果只差0.01。去掉四舍五入,简化代码逻辑的收益更大。
思路
既然能复用显示整数的代码,那么最简单的思路就是把浮点的整数和小数部分分别转换成整数,打印的时候中间加个小数点就好了,只有一个小问题。比如,要把 3.03 转换成两个整数,整数部分 = 3,没问题;小数部分是 03,如果按整数打印,左侧的0 就没了,整体打印出来变成3.3。
好在我的整数显示函数功能还比较齐全,可以设置数字右对齐,并且左侧补0。那么只需要在打印小数部分时,设置数字右对齐,且数字长度等于小数精度。比如,打印 3.03,保留两位小数;打印 03 的时候,数字长度设置为2,右对齐,小数部分3 会被放在右边,左侧补上一个0,就变成3.03 了。
这个办法没有引入什么额外的运算逻辑,只用了整数函数现有的功能,而且右对齐逻辑也挺简单的。
至于分离出整数的办法,如果精度设置为2,即保留两位小数;那就让小数部分乘100,然后转换成整数。比如3.333,给0.333 * 100 == 33.3,转成整数就是33。这样就不需要除法,只需要一次浮点乘法,以及分别对整数和小数部分做一次整数转换。感觉已经很难再想出更简洁高效的方法了。
实现
简单起见,不把整套单片机程序搬过来,只验证这个浮点函数;字符串打印到控制台,用cout
替代整数显示函数,printf
好像不能设置右对齐并用0 填充,所以就用cout 了。
cpp
#include <iostream>
#include <iomanip>
#include <math.h>
#include <stdint.h>
// 整数函数能打印的最大数值
#define NUMBER_MAX (int32_t)(((uint32_t)(0) - 1) << 1 >> 1)
/**
* @brief 打印浮点
*
* @param f
* @param precision 保留几位小数
*/
void put_float(float f, uint8_t precision) {
using namespace std;
if (isnan(f)) {
// 实际单片机程序里不需要加这个换行符
cout << "NAN" << endl;
return;
}
if (isinf(f)) {
cout << "INF" << endl;
return;
}
if (abs(f) > ((float)(NUMBER_MAX))) {
// 如果浮点数太大,显示溢出OVF
cout << "OVF" << endl;
return;
}
// 根据选择的精度,计算要乘的因数
uint32_t multiply_factor = 1;
switch(precision) {
case 0:
precision = 1;
case 1:
multiply_factor = 10;
break;
case 2:
multiply_factor = 100;
break;
case 3:
multiply_factor = 1000;
break;
default:
for (uint8_t i = precision; i > 0; --i) {
multiply_factor *= 10;
}
}
uint32_t left_part = (uint32_t)(f); // 整数部分
f -= left_part;
f *= multiply_factor;
uint32_t right_part = (uint32_t)(f); // 小数部分
cout << left_part << '.';
// 小数点右边
// 打印整数时默认会忽略前导零,而小数的前导零不能忽略
// 临时设置数字格式为右对齐,长度为精度,
// 再把填充字符设为'0',把前导零补上
cout << right << setfill('0') << setw(precision);
cout << right_part << endl;
}
int main() {
using namespace std;
cout << "Float = " << 33.0 + 0.1 / 3 << endl;
put_float(33.0 + 0.1 / 3, 4);
return 0;
}
主程序里设置输出精度是4,也就是保留4 位小数,输出结果:
Float = 33.0333
33.0333
put_float
和cout
转换的结果一致。现在这个函数还有一个小问题,如果设置了整数按左对齐,小数部分就可能跟整数部分中间分开,比如:
cpp
int main() {
using namespace std;
cout << "Float = " << 33.0 + 0.1 / 3 << endl;
cout << left << setw(5);
put_float(33.0 + 0.1 / 3, 4);
return 0;
}
主程序在调用put_float
之前,先设置数字左对齐,宽度是5,用空格填充。运行后效果就是这样:
Float = 33.0333
33 .0333
打印出来,整数部分33 和小数部分中间分开了。这个问题要处理的话比较麻烦,也不能说禁止左对齐,不如就当作是个功能好了,就这么设定的,具有风格独特的显示格式[doge];好处是,如果一列显示多个浮点数,可以把小数点的位置对齐。