极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 21798|回复: 5

【教程】用String( )做类似printf("aa=%d, BB=%f",Vint, vFloat)格式化输出

[复制链接]
发表于 2015-4-5 12:17:45 | 显示全部楼层 |阅读模式
本帖最后由 tsaiwn 于 2015-4-5 14:14 编辑

虽然 Arduino 可以用 sprintf(charArray, "format", data, data2, ...);
但请注意,  在 Arduino, 它的 "format" 不可用  %f, %lf, %ll .., 不好用 !
用 C++ 的 String( ) 串接, 简单又好用 !

(一)简单用法, 不注重格式, 只要有印出就好
    要格式化输出, 最简单的方法就是直接用 C++ 的 String( ) 串接功能即可:

   int height=168;
   float weight=72.5;
   Serial.println(String("")+"Your Height="+height +   ", and Weight=" + weight);
说明:
      只要第一个是 String(""), 之后不论整数 int, long, 实数 float 等都会自动转为字符串, 用 + 串接在一起 !
缺点:
      float 小数点后会印出几位无法控制 :-(
   (注意 UNO 如果用 double 其实会被偷改为 float )

(二)那如果是要印到 LCD 或 SoftwareSerial  软串口甚至 SPI  呢?
    简单, 先放到 String 字符串即可, 之后爱怎样就怎样 :

   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height +
                       ", and Weight=" + weight;

   Serial.println(gy);  // 印到串口
   LCD.print(gy);  // 假设你已经有 LCD. 可以用

(三)如果实数float要印出小数点后两位呢?
    也很简单, 只是看起来有点麻烦而已(其实你用 Serial.print(float)  它也是偷偷这样做 !):

   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height;
    gy += ", and Weight=";
    // 开始处理 float weight 的值
    long tmp = weight;
    gy += tmp; // 整数部分
    gy += ".";  // 小数点, 废话
    tmp = (weight -tmp)*100+0.5;  //  小数, 四捨五入
    if(tmp >= 100) tmp=99; // 防错, Also see (四)的 防错
    if(tmp < 10) gy += "0";
    gy += tmp;
   Serial.println(gy);  // 印到串口
   LCD.print(gy);  // 假设你已经有 LCD. 可以用

(四)如果实数float 只要印出小数点后一位呢?
    也很简单, 参考著(三)写应该就会啦:

   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height;
    gy += ", and Weight=";
    // 开始处理 float weight 的值
    long tmp = weight;  // 整数部分
    long yytmp = (weight -tmp)*10+0.5;  // 小数, 四捨五入
    if(yytmp >= 10) {  // 防错
       tmp += 1;    // 整数部分 + 1
       yytmp = 0;
    }
    gy += tmp; // 整数部分
    gy += ".";  // 小数点, 废话
    //if(tmp < 100) gy += "0";    // 小数点后 >= 3位
    //if(tmp < 10) gy += "0";    // 小数点后 >=  2位
    gy += tmp;
   Serial.println(gy);  // 印到串口
   LCD.print(gy);  // 假设你已经有 LCD. 可以用

(五)如果实数float 要印出小数点后三位呢?
    ㄟ, 阿我前面已经有(三)印到两位和(四)印到一位的范例,
    参考著写应该就会啦 !
    不过还有一简单写法,
    就是先把 float 稍微加工处理后用 String 串接 :

     float weight=72.12666;  // 故意
     long prec = 1000; // 三位
     float aw = ((long)(weight*prec+0.5))/1.0/prec;    //注意 /1.0/prec;  四捨五入
     String ans = String("Weight=") + aw;
     Serial.println(ans);
     LCD.print(ans);
   优点:
          简单, 要小数点后两位就 prec = 100;   要小数点后一位就 prec = 10;
       但请注意, 上面那 /1.0/prec 是必要的, 是让它先变为 float;
       因为 /1.0 左边已经是 long 整数, 如果不先 /1.0 就直接做 /prec  则
       变 long / long 將是 long 不对, 因为 38/10 是 3 不是 3.8喔 !
   缺点:
       因为 Arduino 考虑 MCU 能力有限, 无法用 double,
       即使你写 double, 也会被偷改为 float;
       可是 float 的有效精准度只有 7 位左右,
       就是说 从最左边不是 0 的位开始只有七位到八位是可以信任的,
       也就是 float x = 123.45678999; 与 float x = 123.45678922;
       其实几乎是一样的, 这原因是因为 float 只有用 32 bits表示,
       其中 23 bits 加上一个隱藏的 bit 共 24 bits 存有效值 significand;
       所以把  float 转换为 binary 之后只能保留左边 24 bits,
       这等於 binary 准確 24位, 相当於十进制的 24*0.3010=7.2位!
      参考:  http://zh.wikipedia.org/wiki/IEEE_754
         (如看不到, 请用百度查询 "IEEE 754")

=============================================================
回复

使用道具 举报

 楼主| 发表于 2015-4-5 12:28:33 | 显示全部楼层

补充

没有float实数, 则可以用 sprintf( )
但你的程序码就会多大约 1.5KB !
注意在 Arduino 上不可以用在 float, double, 以及 long long 都不行!

int height=168;
float weight=72.5;
char cgy[66];  // C 字符串; 自己要注意是否 66 bytes 够用, 別忘了 C 字符串须要多一 char 放 '\0' (就是整数 0)表示结束!!!
long wtmp = weight + 0.5; // 因为Arduino 的 sprintf( )  不可用 float/double; 只好似捨五入为整数 long
sprintf(cgy, "Your Height=%d, and Weight=%ld", height, wtmp);
/// 注意 %d 是给 int 用(2 bytes); long 要用 %ld 才对喔 !
Serial.println(cgy);  // 印到串口
LCD.print(cgy);  // 假设你已经有 LCD. 可以用

参考: vfprintf()
关於 format 格式字符串:
  http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1gaa3b98c0d17b35642c0f3e4649092b9f1.html

  http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1ga6017094d9fd800fa02600d35399f2a2a.html

  http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1ga2b829d696b17dedbf181cd5dc4d7a31d.html
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-5 12:33:51 | 显示全部楼层
本帖最后由 tsaiwn 于 2015-4-5 12:36 编辑
tsaiwn 发表于 2015-4-5 12:28
补充

没有float实数, 则可以用 sprintf( )


再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 float,  所以其实没有 double 可用 !
    要类似 %6.3f 印float实数也不难,
偷用 AVR Library 內的 dtostrf( ) 先把 float 转为 C 的字符串即可:
int height=168;
float weight=72.12666;  // 故意
char cgy[66];
char www[22];  // 放 weight 体重, 故意用 22 bytes, 注意位置一定要够用 !
///
dtostrf(weight, 6, 3, www);  // 相当於 %6.3f
String gy = String("")+"Your Height="+height +
      ", and Weight=" + www);  // 注意 www 是 weight 的字串格式
Serial.println(gy);  // 印到串口
LCD.print(gy);  // 假设你已经有 LCD. 可以用
/// 或是
sprintf(cgy, "Your Height=%d, and Weight=%s", height, www);
// 注意 www 是用 %s "印" 入 cgy 字符串內部!
// 这样 cgy 也是字符串, 是 C 的字符串, 不是 C++ 的 String
// 也可用来 print
Serial.println(cgy);  // 印到串口
LCD.print(cgy);  // 假设你已经有 LCD. 可以用

参考:
   https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/libc/stdlib/dtostrf.c

   https://android.googlesource.com/toolchain/avr-libc/+/edcf5bc1c8da8cc4c8b560865d2a54b73c1b51d3/avr-libc-1.7.1/libc/stdlib/dtoa_prf.c
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-5 15:41:35 | 显示全部楼层
本帖最后由 tsaiwn 于 2015-4-8 23:06 编辑
tsaiwn 发表于 2015-4-5 12:33
再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 f ...



再补充 .. 再补充 ..

为何 Arduino 的 printf/sprintf 不支援 float / double / long long ?

   (A)前面说过 Arduino 的 double 根本是骗人的,
      因为 Arduino 的 CPU 是 8 bit CPU, 意思是大部分指令都只处理 8 bit,
      float 用 32 bit 已经很辛苦, double 用 64 bit岂不更辛苦 !?
      参考用百度查询 "IEEE 754" 看看就知道了!
   (B)在標准 C 的程序库大约有一百多个函数(不算入 C++ 的喔),
      其中很多函数都是一行两行就做完了,
      但是, 標准 C 的 printf( ) 却多达两千多行(包括相关的sprintf/vsprintf等) !
      想一想, 如果 Arduino 也让你真的可以像在 PC 或大型电脑上
      使用 printf( )/sprintf( ) 的所有功能,
      那你的 32KB 程序码空间可能就去掉六分之一囉!
   (C)其实就算是现在精简版的 printf/sprintf 也占用约1.5KB,
      只要你的程序码用了一行 sprintf( ) 或 printf( ),
      你编译出的机器码就会多大约 1.5KB,
      当然多用几行並不会再增加太多(只是多了参数传递与函数调用)!
   (D)前面用到的 dtostrf( ) 本身也占用大约 1.5KB;
      所以你可以故意用一行 dtostrf( ) 再重新编译看看它佔多少空间 !?
   (E)啥? 你说反正 Flash/ROM 程序码空间有 31.5KB 不怕喔 !?
      (以 Arduino IDE 1.0.6 为例, 有 32256 Bytes = 32768 - 512 Byte BootLoader)
      如果简单程序码当然没问题啦 !
      要注意, 使用C++的 String 字符串也是要多用大约 1.5KB的空间!
         当你隨著传感器或GSM/Wifi/Ethernet 一直加上去,
      你会发现很快的, 31.5KB 好像不太够用了!
      这时你就要想办法儘量节省著用囉,
      能不用的当然就不用 !
      例如, 用了 String 了(这好像比较好用吧),
      那就儘量不要再用 dtostrf( ) 以及 sprintf( );
         如果空间真的不够用, 且你须要更快的执行效能,
      则可能也不要用 String 类別, 因为 String 类別不但多用了 1.5KB,
      而且它比传统 C 的字符串(就是 char array[ ])处理慢数倍! (这我以前有写过 !)
      只是传统 C 的 strcat( ), strcpy( ), strncpy( ) 使用要很小心,
      且对大多数 Arduino 的入门者也不好用 :-(
      所以空间足够时当然先用 C++ 的 String 来处理方便多了 !
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-5 15:45:55 | 显示全部楼层
tsaiwn 发表于 2015-4-5 12:33
再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 f ...


如果你要研究 dtostrf( ) 以及其用到的 dtoa_prf ( );
可以参考:

   https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/libc/stdlib/dtostrf.c

  https://android.googlesource.com/toolchain/avr-libc/+/edcf5bc1c8da8cc4c8b560865d2a54b73c1b51d3/avr-libc-1.7.1/libc/stdlib/dtoa_prf.c


////// 为了方便想研究的查看, 复製到以下...

char *
dtostrf (double val, signed char width, unsigned char prec, char *sout)
{
    unsigned char flags;

    /* DTOA_UPPER: for compatibility with avr-libc <= 1.4 with NaNs        */
    flags = width < 0 ? DTOA_LEFT | DTOA_UPPER : DTOA_UPPER;
    dtoa_prf (val, sout, abs(width), prec, flags);
    return sout;
}
// If precision is < 0, the string is left adjusted with leading spaces.
// If precision is > 0, the string is right adjusted with trailing spaces.

//////////////////////////////////////////////////////////////////////////////
// 以下是  int dtoa_prf ( )

#include "ftoa_engine.h"
#include "dtoa_conv.h"
#include "sectionname.h"

int
dtoa_prf (double val, char *s, unsigned char width, unsigned char prec,
          unsigned char flags)
{
    int exp;
    int n;
    unsigned char vtype;
    unsigned char sign;
    unsigned char ndigs;
    unsigned char buf[9];
    ndigs = prec < 60 ? prec + 1 : 60;
    exp = __ftoa_engine (val, (char *)buf, 7, ndigs);
    vtype = buf[0];
    sign = 0;   
    if ((vtype & (FTOA_MINUS | FTOA_NAN)) == FTOA_MINUS)
        sign = '-';
    else if (flags & DTOA_PLUS)
        sign = '+';
    else if (flags & DTOA_SPACE)
        sign = ' ';
    if (vtype & FTOA_NAN) {
        ndigs = sign ? 4 : 3;
        width = (width > ndigs) ? width - ndigs : 0;
        if (!(flags & DTOA_LEFT)) {
            while (width) {
                *s++ = ' ';
                width--;
            }
        }
        if (sign) *s++ = sign;
        if (flags & DTOA_UPPER) {
            *s++ = 'N';  *s++ = 'A';  *s++ = 'N';
        } else {
            *s++ = 'n';  *s++ = 'a';  *s++ = 'n';
        }
        while (width) {
            *s++ = ' ';
            width--;
        }
        *s = 0;
        return DTOA_NONFINITE;
    }
    if (vtype & FTOA_INF) {
        ndigs = sign ? 4 : 3;
        width = (width > ndigs) ? width - ndigs : 0;
        if (!(flags & DTOA_LEFT)) {
            while (width) {
                *s++ = ' ';
                width--;
            }
        }
        if (sign) *s++ = sign;
        if (flags & DTOA_UPPER) {
            *s++ = 'I';  *s++ = 'N';  *s++ = 'F';
        } else {
            *s++ = 'i';  *s++ = 'n';  *s++ = 'f';
        }
        while (width) {
            *s++ = ' ';
            width--;
        }
        *s = 0;
        return DTOA_NONFINITE;
    }
    n = (sign ? 1 : 0) + (exp>0 ? exp+1 : 1) + (prec ? prec+1 : 0);
    width = width > n ? width - n : 0;

    if (!(flags & DTOA_LEFT) && !(flags & DTOA_ZFILL)) {
        while (width) {
            *s++ = ' ';
            width--;
        }
    }
    if (sign) *s++ = sign;
    if (!(flags & DTOA_LEFT)) {
        while (width) {
            *s++ = '0';
            width--;
        }
    }
    ndigs += exp;                /* exp is resticted approx. -40 .. +40        */
    sign = buf[1];
    if ((vtype & FTOA_CARRY) && sign == '1')
        ndigs -= 1;
    if ((signed char)ndigs < 1)
        ndigs = 1;
    else if (ndigs > 8)
        ndigs = 8;
    n = exp > 0 ? exp : 0;
    do {
        if (n == -1)
            *s++ = '.';
        flags = (n <= exp && n > exp - ndigs) ? buf[exp - n + 1] : '0';
        if (--n < -prec)
            break;
        *s++ = flags;
    } while (1);
    if ( n == exp && (sign > '5' || (sign == '5' && !(vtype & FTOA_CARRY))) )
        flags = '1';
    *s++ = flags;

    while (width) {
        *s++ = ' ';
        width--;
    }
    *s++ = 0;
    return 0;
}

回复 支持 反对

使用道具 举报

发表于 2015-4-6 13:20:07 | 显示全部楼层
谢谢分享学习一下
回复 支持 反对

使用道具 举报

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

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-17 22:39 , Processed in 0.037030 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表