Treating 0 as a significant figure in printf %g fo

2019-05-21 13:08发布

问题:

I'm trying to print floating point numbers 1.2345678, 0.1234567, 123.45678 using printf in such a way that only the first n characters are printed. I want these number to line up. The %g format specifier in printf("%*g", n, var) does this but the specifier is not treating the 0 in 0.1234567 as a significant figure. This causes the alignment of 0.1234567 to go off wrt to the other two figures.

What's the best way to align the numbers in the formats given. Either by treating 0 as significant with %g or using some other method?

回答1:

By definition leading zeros are not significant figures. If you want the first N digits to be printed, including leading zeros, convert first the numbers to strings and use printf("%.*s", n, var_as_string).

This way, the first n characters are indeed printed, as you asked. Of course the decimal mark ., as any other character, is now significant, contrary to %g.



回答2:

A brute force method tries various precisions of %.*g until success.

The major obstacle to calculating the width of the print out before calling printf() is that corner cases exist like 0.9995. The subtle effects of rounding make it that it is simpler to call sprintf() a subsequent time. Additional code could be uses to make a better initial guess rather than starting at prec = n - 1

int g_print(double x, int n) {
  int width;
  char buffer[n + 1];
  for (int prec = n - 1; prec >= 0; prec--) {
    width = snprintf(buffer, sizeof buffer, "%.*g", prec, x);
    // printf("  n:%2d p:%2d w:%2d <%s>\n", n, prec, width, buffer);
    if (width > 0 && (unsigned) width < sizeof buffer) {
      return printf("%2d <%s>\n", n, buffer);
    }
  }
  // Try with %f
  width = snprintf(buffer, sizeof buffer, "%.0f", x);
  if (width > 0 && (unsigned) width < sizeof buffer) {
    return printf("%2d <%s>\n", n, buffer);
  }
  printf("%2d Fail %e\n", n, x);
  return 0; // fail
}

void g_print_test(double x) {
  // Need at least  width 7 for all values
  for (int n = 8; n >= 1; n--) {
    if (g_print(x, n) == 0)
      break;
  }
}


int main() {
  g_print_test(1.2345678);
  g_print_test(0.1234567);
  g_print_test(123.45678);
  g_print_test(DBL_MAX);
  g_print_test(-DBL_MAX);
  g_print_test(-DBL_MIN);
  return 0;
}

Output

 n  text...
 8 <1.234568>
 7 <1.23457>
 6 <1.2346>
 5 <1.235>
 4 <1.23>
 3 <1.2>
 2 <1>
 1 <1>
 8 <0.123457>
 7 <0.12346>
 6 <0.1235>
 5 <0.123>
 4 <0.12>
 3 <0.1>
 2 <0>
 1 <0>
 8 <123.4568>
 7 <123.457>
 6 <123.46>
 5 <123.5>
 4 <123>
 3 <123>
 2 Fail 1.234568e+02
 8 <1.8e+308>
 7 <2e+308>
 6 <2e+308>
 5 Fail 1.797693e+308
 8 <-2e+308>
 7 <-2e+308>
 6 Fail -1.797693e+308
 8 <-2e-308>
 7 <-2e-308>
 6 <-0>
 5 <-0>
 4 <-0>
 3 <-0>
 2 <-0>
 1 Fail -2.225074e-308


回答3:

I want these number to line up. The %g format specifier in printf("%*g", n, var) does this

No, it doesn't - with %*g you're only specifying a minimum field width… In no case does a nonexistent or small field width cause truncation of a field; if the result of a conversion is wider than the field width, the field is expanded to contain the conversion result. [from man 3 printf]

But perhaps you meant %.*g, specifying the precision …, the maximum number of significant digits, which behaves according to your problem description.

What's the best way to align the numbers in the formats given. Either by treating 0 as significant with %g or using some other method?

What's the best way depends on the value range of the numbers. If we have just positive numbers similar to those you posted, we could simply reduce the precision by one for a leading zero, e. g.:

    printf("%.*g", n-(var<1), var);