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

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

相关推荐
朝朝又沐沐2 小时前
算法竞赛阶段二-数据结构(36)数据结构双向链表模拟实现
开发语言·数据结构·c++·算法·链表
得单片机的运2 小时前
STM32的蓝牙通讯(HAL库)
stm32·单片机·嵌入式硬件·蓝牙
薰衣草23333 小时前
一天两道力扣(6)
算法·leetcode
剪一朵云爱着3 小时前
力扣946. 验证栈序列
算法·
遇见尚硅谷3 小时前
C语言:*p++与p++有何区别
c语言·开发语言·笔记·学习·算法
国科安芯3 小时前
抗辐照芯片在低轨卫星星座CAN总线通讯及供电系统的应用探讨
运维·网络·人工智能·单片机·自动化
天天开心(∩_∩)4 小时前
代码随想录算法训练营第三十二天
算法
YouQian7724 小时前
(AC)缓存系统
算法·缓存
艾莉丝努力练剑4 小时前
【数据结构与算法】数据结构初阶:详解排序(二)——交换排序中的快速排序
c语言·开发语言·数据结构·学习·算法·链表·排序算法
科大饭桶4 小时前
数据结构自学Day13 -- 快速排序--“前后指针法”
数据结构·算法·leetcode·排序算法·c