3

I'd like to print floats via printf as follows:

  • (1) right-align into a string of given width (here 8)
  • (2) show relevant decimal digits if possible, but no unneccesary trailing 0s after .
  • (3) also do this for rounded values, i.e. format 1.0 as "1."

With g I cannot achieve (3), with f I cannot achieve (2,3).

From the docs it seems that # would do the trick for (3)

Used with a, A, e, E, f, F, g or G it forces the written output to contain a decimal point even if no more digits follow. By default, if no digits follow, no decimal point is written.

So #g can achieve (3), but it unfortunately does more than is written there: it also breaks the feature (2) by removing also relevant decimal digits (see below).

Here are some examples I tried, the last line shows what I am looking for:

number 1.0 -1.0 1.9 -999.1 1.000001
%8.2g 1 -1 1.9 -1e+03 1
%8.g 1 -1 2 -1e+03 1
%8g 1 -1 1.9 -999.1 1
%#8.2g 1.0 -1.0 1.9 -1.0e+03 1.0
%#8.g 1. -1. 2. -1.e+03 1.
%#8g 1.00000 -1.00000 1.90000 -999.100 1.00000
%8.2f 1.00 -1.00 1.90 -999.10 1.00
%8.f 1 -1 2 -999 1
%8f 1.000000 -1.000000 1.900000 -999.099976 1.000001
%#8.2f 1.00 -1.00 1.90 -999.10 1.00
%#8.f 1. -1. 2. -999. 1.
%#8f 1.000000 -1.000000 1.900000 -999.099976 1.000001
???? 1. -1. 1.9 -999.1 1.000001

Can somebody help how to achieve the wanted output in the last line?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
flonk
  • 3,726
  • 3
  • 24
  • 37
  • 1
    I think you might have to code your own formatting function. You could use %8f to printf to a buffer, count the number of trailing zeroes, then compute the number of places to the right it needs to be shifted. – Simon Goater Feb 27 '23 at 11:20
  • 8 characters, only? So -1.2345e-100 gets printed as "-1.e-100" with only 1 significant digit? – chux - Reinstate Monica Feb 27 '23 at 12:22
  • @chux You may regard this question generalized for any length instead of just `8`, but yes, this is what I want. Note that your example value would anyways become indistinguishible from zero if you apply any nontrivial numerical operation to it and if You treat any real world problem (finance, physics from quantum to astronomical scales) there would be no use for such a precision as long as suitable units are used. – flonk Feb 27 '23 at 13:44
  • @flonk I now see the "print float", so 3 digits exponents are not likely. – chux - Reinstate Monica Feb 27 '23 at 18:48

3 Answers3

2

Suggestions:

Modify goal

Use "%8g" as that is standard and closest to OP's goal.

Post process output

"#" in "%g" modifies 2 things: a '.' is always printed and trailing zeroes are not discarded. OP seems to want just the first feature.

Use "%#*.*g", n, n-1, ... and post-process the string.

This is tricky and deserves extensive testing.

Do not

  • Pre-process the float before printing. Edge cases will catch you.

  • Add text in post processing. Edge cases will catch you.

  • "%e" does not provide non-exponential form.

  • "%f" can make for long output with values like -FLT_MAX.

Sample code. Simplifications exists.

#include <stdio.h>
int main() {
  float val[] = {1.0, -1.0, 1.9f, -999.1f, 1.000001f};
  size_t v_n = sizeof val / sizeof val[0];
  puts("????        1.             -1.             1.9          -999.1        1.000001");
  fputs("           ", stdout);
  for (size_t v_i = 0; v_i < v_n; v_i++) {
    double v = val[v_i];
    char s[100];
    int n = 8;
    snprintf(s, sizeof s, "%#*.*g", n, n - 1, v);
    char *dot = strchr(s, '.');
    if (dot) {
      char *begin = dot + 1;
      begin = begin + strspn(begin, "0123456789");
      char *end = begin;
      while (begin[-1] == '0')
        begin--;
      if (begin < end) {
        //printf("xx: <%s> <%s>\n", begin, end);
        size_t len = strlen(end);
        memmove(begin, end, len + 1);
      }
    }
    printf(" %-13s", s);
  }
}

Output

????        1.             -1.             1.9          -999.1        1.000001
           1.           -1.          1.9          -999.1       1.000001     
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Thanks for Your efforts. My main problem with a custom implementation is, that I would loose the flexibility of `printf`, e.g. I have to heavily use it in commands like `printf("%8g%8gSOMETEXT%8g%8g%8g", a, b, x, y, z)`. With a custom implementation, this one-liner (of which there are lots in my code) would become barely readable. At least I *think* this is the case, or am I lacking imagination? – flonk Mar 01 '23 at 07:41
  • In other words, Your code does what I want *technically* for one example, but I don't se how I can combine it with other format-strings in an expression like `"%8g%8g%2d%4f%8g"` where I only want to replace the `%8g`s by the new formatting. Using `printf` would allow for this and using a custom function would not, as long as I don't recode lots of the logic of `printf` itself – flonk Mar 01 '23 at 07:44
  • @flonk I agree that custom code that meets your goal is not as good as modifying your goal to use standard printf specifiers. Consider `#define FMT_F "%8g" .... printf( FMT_F FMT_F "SOMETEXT" FMT_F FMT_F FMT_F , a, b, x, y, z);` to help manage printf formats. There really exist many ways to approach this like [function call with automatic space allocated per specier](https://stackoverflow.com/a/34641674/2410359), yet you have the advantage of knowing the overall goals and I do not. – chux - Reinstate Monica Mar 01 '23 at 07:54
  • @flonk So code ends up with `printf("%s%s%2d%4f%8s", F(a), F(b), x, y, F(z))` where `F(x)` is a macro that calls a helper function with `x` and the needed buffer. There you can craft your helper function to do any specialized formatting you want as it returns a `char *` ready for `"%s"`. – chux - Reinstate Monica Mar 01 '23 at 07:59
  • 1
    considering Your remark about my "advantage", this was not meant as a critique ;) Your suggested `#define` strategy sounds very interesting, I'll give this a deeper look at the weekend, thanks. – flonk Mar 03 '23 at 10:14
0

Some test for for anyone to use.
It may help this investigation.

#include <stdio.h>
int main() {
  float val[] = {1.0, -1.0, 1.9f, -999.1f, 1.000001f};
  size_t v_n = sizeof val / sizeof val[0];
  const char *format[] = {"%8.2g", "%8.g", "%8g", "%#8.2g", "%#8.g", "%#8g",
      "%8.2f", "%8.f", "%8f", "%#8.2f", "%#8.f", "%#8f", };
  size_t f_n = sizeof format / sizeof format[0];
  for (size_t f_i = 0; f_i < f_n; f_i++) {
    char f[100];
    snprintf(f, sizeof f, "<%s>", format[f_i]);
    printf("%-13s, ", f);
    for (size_t v_i = 0; v_i < v_n; v_i++) {
      double v = val[v_i];
      char s[100];
      snprintf(s, sizeof s, f, v);
      printf("%-13s", s);
    }
    puts("");
  }
}

Output

<%8.2g>      , <       1>   <      -1>   <     1.9>   <  -1e+03>   <       1>   
<%8.g>       , <       1>   <      -1>   <       2>   <  -1e+03>   <       1>   
<%8g>        , <       1>   <      -1>   <     1.9>   <  -999.1>   <       1>   
<%#8.2g>     , <     1.0>   <    -1.0>   <     1.9>   <-1.0e+03>   <     1.0>   
<%#8.g>      , <      1.>   <     -1.>   <      2.>   < -1.e+03>   <      1.>   
<%#8g>       , < 1.00000>   <-1.00000>   < 1.90000>   <-999.100>   < 1.00000>   
<%8.2f>      , <    1.00>   <   -1.00>   <    1.90>   < -999.10>   <    1.00>   
<%8.f>       , <       1>   <      -1>   <       2>   <    -999>   <       1>   
<%8f>        , <1.000000>   <-1.000000>  <1.900000>   <-999.099976><1.000001>   
<%#8.2f>     , <    1.00>   <   -1.00>   <    1.90>   < -999.10>   <    1.00>   
<%#8.f>      , <      1.>   <     -1.>   <      2.>   <   -999.>   <      1.>   
<%#8f>       , <1.000000>   <-1.000000>  <1.900000>   <-999.099976><1.000001>   
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

You cannot obtain the desired output with printf alone, but you can use snprintf and adjust the output this way:

int format_number(double v) {
    char buf[500];
    int len = snprintf(buf, sizeof buf, "%f", v);
    while (len > 0 && buf[len - 1] = '0')
        buf[--len] = '\0';
    return printf("%8s", buf);
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189