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

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

相关推荐
QAQo7T1 小时前
Python组蓝桥杯备赛超详细知识点总结笔记_排序算法篇
笔记·python·算法·蓝桥杯·排序算法
千里之行,始于足下sanhai1 小时前
P8686 [蓝桥杯 2019 省 A] 修改数组 - 数字魔法大冒险 题解
c++·算法·动态规划
小星星闪亮登场1 小时前
Codeforces Round 1115 ( Div. 2)
数据结构·c++·算法·贪心算法·排序算法·codeforces
计算机小白一个1 小时前
蓝桥杯 Java B 组之哈希表应用(两数之和、重复元素判断)
java·数据结构·算法·蓝桥杯
Loge编程生活2 小时前
打卡信奥刷题(3449)用C++实现信奥题 P10429 [蓝桥杯 2024 省 B] 拔河
开发语言·数据结构·c++·算法·青少年编程
2601_957174653 小时前
大模型算法 简单 容易 用这个方法
算法
VALENIAN瓦伦尼安教学设备3 小时前
激光对中仪采购要点
数据库·嵌入式硬件·算法
芯岭技术3 小时前
OM6625A芯片使用PY32 link烧录程序流程操作介绍
嵌入式硬件·物联网
Forever Nore5 小时前
LeetCode 1 两数之和
算法·leetcode·职场和发展
To_OC5 小时前
LC 3 无重复字符的最长子串:从入门滑动窗口到优化写法,再也不怕面试官追问
javascript·算法·leetcode