[经验分享] 简单快速的浮点数转字符串算法,适合单片机环境

[复制链接]
1032|0
磨砂 发表于 2025-11-8 10:41 | 显示全部楼层 |阅读模式
目的是在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。这样就不需要除法,只需要一次浮点乘法,以及分别对整数和小数部分做一次整数转换。感觉已经很难再想出更简洁高效的方法了。

四舍五入
很简单,如果要保留一位小数,就在取整之前先给浮点数加0.05。比如,要显示的小数是1.552,加了0.05 变成1.602,然后按一位小数精度取整,整数部分是1,小数部分6。依次类推,保留两位小数就先加0.005。

实现
简单起见,不把整套单片机程序搬过来,只验证这个浮点函数;字符串打印到控制台,用cout 替代整数显示函数,printf 好像不能设置右对齐并用0 填充,所以就用cout 了。


#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;
    float round_bit = 0.0;
    switch(precision) {
        case 0:
            precision = 1;
        case 1:
            multiply_factor = 10;
            round_bit = 0.5 / 10;
            break;
        case 2:
            multiply_factor = 100;
            round_bit = 0.5 / 100;
            break;
        case 3:
            multiply_factor = 1000;
            round_bit = 0.5 / 1000;
            break;
        default:
            for (uint8_t i = precision; i > 0; --i) {
                multiply_factor *= 10;
            }
            round_bit = 0.5 / multiply_factor;
    }

    // 四舍五入
    f += round_bit;

    // 取整
    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 转换的结果一致。现在这个函数还有一个小问题,如果设置了整数按左对齐,小数部分就可能跟整数部分中间分开,比如:

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];好处是,如果一列显示多个浮点数,可以把小数点的位置对齐。
————————————————
版权声明:本文为CSDN博主「刻BITTER」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Etberzin/article/details/147689218

您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

4415

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部