简单快速的浮点数转字符串算法,适合单片机环境

目的是在OLED 屏幕上显示浮点数,有几个设计要求:

  1. 我已经有一个现成的能显示整数的函数,希望尽量复用;
  2. 尽量不使用除法;
  3. 不需要考虑小数四舍五入的问题;

我觉得小数四舍五入其实很多时候没什么用处,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_floatcout 转换的结果一致。现在这个函数还有一个小问题,如果设置了整数按左对齐,小数部分就可能跟整数部分中间分开,比如:

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];好处是,如果一列显示多个浮点数,可以把小数点的位置对齐。

相关推荐
贾全14 分钟前
第十章:HIL-SERL 真实机器人训练实战
人工智能·深度学习·算法·机器学习·机器人
GIS小天29 分钟前
AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年7月4日第128弹
人工智能·算法·机器学习·彩票
满分观察网友z1 小时前
开发者的“右”眼:一个树问题如何拯救我的UI设计(199. 二叉树的右视图)
算法
Natsume17101 小时前
嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例
c语言·驱动开发·stm32·嵌入式硬件·mcu·架构·github
MeshddY2 小时前
(超详细)数据库项目初体验:使用C语言连接数据库完成短地址服务(本地运行版)
c语言·数据库·单片机
m0_555762902 小时前
STM32常见外设
stm32·单片机·嵌入式硬件
森焱森2 小时前
无人机三轴稳定化控制(1)____飞机的稳定控制逻辑
c语言·单片机·算法·无人机
循环过三天2 小时前
3-1 PID算法改进(积分部分)
笔记·stm32·单片机·学习·算法·pid
闪电麦坤953 小时前
数据结构:二维数组(2D Arrays)
数据结构·算法
凌肖战3 小时前
力扣网C语言编程题:快慢指针来解决 “寻找重复数”
c语言·算法·leetcode