printf的优雅自定义小数点(不是基于语言环境)?

时间:2011-10-25 10:09:58

标签: floating-point printf pretty-print

我正在为电子应用输出电容值,我想以行业标准的方式格式化值,即2 s.f.使用SI标量前缀替换小数点,例如4.7µF变为4u7220nF变为220n。优雅似乎正在逃避我,我能想到的最好的事情是这个笨拙的混乱:

float cap = 4.7 * 10^(-6); // 4.7µF
...
// work out the multiplication factor and appropriate SI scalar symbol
// here we hardcode it
float mul = 10^(-6);
char symbol = 'u';
...
// split cap/mul to integer and integral parts
float pre, post;
pre = modf( cap/mul, &post );
// simple case, we have no integral part at 2 s.f.
// (accounting for rounding errors)
if ( pre >= 10.0f || post < 0.05f )
{
    printf("%d%c\n", pre, symbol )
}
else
{
    // convert post to a single digit integer by 
    // rounding to 1sf and multiplying by 10
    post *= 10.0f;
    post = floor( post+0.5f );
    // print
    printf( "%.0f%c%.0f\n", pre, symbol, post );
}

其他人可以做得更好吗?

1 个答案:

答案 0 :(得分:0)

使用sprintf("+4.1e")进行主转换,然后对字符串进行后处理。

#include <stdio.h>

// Convert value to text showing 2 significant digits and metric prefix for decimal point
int EEPrint(char dest[6], double x, char Unit) {
  char buffer[1 + 1 + 1 + 1 + 1 + 1 + 4 + 1 + 8 /* spare */];  // note 1
  snprintf(buffer, sizeof(buffer), "%+4.1e", x);  // note 2
  int I, F, Expo;
  char Extra;
  if ((sscanf(buffer, "%2d%*[^0-9]%1de%d%c", &I, &F, &Expo, &Extra) != 3)
      || (Expo < -24) || (Expo > +26) || (!dest)) { // note 3
    return 1; // fail
  }
  Expo -= -24; // a yocto is power(10,-24)
  static char Prefix[] = "yzafpnum.kMGTPEZY";  // Greek mu is '\u03bc'
  Prefix[8] = Unit;  // Replace no-prefix with unit
  char DP = Prefix[Expo / 3];
  // note 4
  switch (Expo % 3) {
    case 0: snprintf(dest, 6, F ? "%d%c%d" : "%d%c", I, DP, F); break; // Note 5
    case 1: snprintf(dest, 6, "%d%d%c", I, F, DP); break;
    case 2: snprintf(dest, 6, "%d%d0%c", I, F, DP); break;
  }
  return 0;
}

/*  General
 *  When the SI prefix is power(10,0) the typical symbol used in electronic notation
 *  is 'R' for resistors, 'L' for inductors, etc.
 *  The longest `dest` string occurs with results like "-120k".
 *  The shortest `dest` string occurs with results like "0R".
 *  note 1
 *  buffer size [1 sign][1 digit][1+ dp][1 digit]e[1 sign][4 digit][1 NUL][8 spare]
 *  note 2
 *  Use sprintf() to do the heavy lifting to get the **best** double to text conversion.
 *  All rounding, +/-0, Nan , Inf, sub-normal issues are well defined.
 *  The rest of the code deals with textual shifting and exponent replacement.
 *  note 3
 *  Nan , Inf detected and rejected.
 *  very small (<= 9.949990e-25) and very large (>= 9.950010e+26) rejected.
 *  NULL dest rejected.
 *  negative numbers accepted.
 *  -0 loses its sign.  To retain, make I a double and change formats
 *  NOT dependent on decimal point being a '.'.
 *  'Extra' detects unexpected extra text and rejects conversion.
 *  note 4
 *  At this point there are _many_ ways to proceed.
 *  I went for the method that is easy to alter and maintain.
 *  note 5
 *  OP's appears to want values like 1,000 --> "1k" rather than "1k0".
 *  I coded per OP, but recommend showing 2 significant digits in this case.
 */

#include <float.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>

static int EEprint_Test(int Expect, double x, char Unit) {
  char buffer[6];
  strcpy(buffer,"Empty");
  int Result = EEPrint(buffer, x, Unit);
  printf("%d %e \"%s\"\n", Result != Expect, x, buffer);
  return Result != Expect;
}

int main() {
  EEprint_Test(0,    -1.0, 'R');
  EEprint_Test(0,    -0.0, 'R');
  EEprint_Test(0,     0.0, 'R');
  EEprint_Test(1,1.23e-30, 'R');
  EEprint_Test(1,0.994999e-24, 'R');
  EEprint_Test(0,0.995001e-24, 'R');
  EEprint_Test(0,1.e-24, 'R');
  EEprint_Test(0,1.23e-20, 'R');
  EEprint_Test(0,1.23e-10, 'R');
  EEprint_Test(0,1.23e-1, 'R');
  EEprint_Test(0,1.23e0, 'R');
  EEprint_Test(0,1.23e1, 'R');
  EEprint_Test(0,1.23e2, 'R');
  EEprint_Test(0,1.23e3, 'R');
  EEprint_Test(0,1.23e4, 'R');
  EEprint_Test(0,1.23e5, 'R');
  EEprint_Test(0,1.23e10, 'R');
  EEprint_Test(0,1.23e20, 'R');
  EEprint_Test(0,9.94999e26, 'R');
  EEprint_Test(1,9.95001e26, 'R');
  EEprint_Test(1,1.23e30, 'R');
  EEprint_Test(1,DBL_MAX, 'R');
  EEprint_Test(1,0.0/0.0, 'R');
  setlocale(LC_ALL,"it_IT");  // comma for DP
  EEprint_Test(0,1.23e0, 'R');
  return 0;
}

#if 0
0 -1.000000e+00 "-1R"
0 -0.000000e+00 "0R"
0 0.000000e+00 "0R"
0 1.230000e-30 "Empty"
0 9.949990e-25 "Empty"
0 9.950010e-25 "1y"
0 1.000000e-24 "1y"
0 1.230000e-20 "12z"
0 1.230000e-10 "120p"
0 1.230000e-01 "120m"
0 1.230000e+00 "1R2"
0 1.230000e+01 "12R"
0 1.230000e+02 "120R"
0 1.230000e+03 "1k2"
0 1.230000e+04 "12k"
0 1.230000e+05 "120k"
0 1.230000e+10 "12G"
0 1.230000e+20 "120E"
0 9.949990e+26 "990Y"
0 9.950010e+26 "Empty"
0 1.230000e+30 "Empty"
0 1.797693e+308 "Empty"
0 nan "Empty"
0 1,230000e+00 "1R2"
#endif