tags:

views:

341

answers:

5

The problem is to statically allocate a buffer large enough to fit a printed double, formatted with %g at maximum precision. This seems like a simple enough task, bu I'm having trouble. The best I have come up with (assuming the number to be printed is x) is

char buf[1 + DBL_DIG + DBL_DIG + 1 + 1 + 1 + DBL_DIG + 1];
int len = sprintf(buf, "%.*g", DBL_DIG, x);

The DBL_DIG macro is from float.h, and apparently it is supposed to indicate the maximum precision for the type double. We need:

  • 1 byte for a negative sign
  • enough bytes to capture the significant digits
  • at most one 'separator' char (comma, etc.) per digit
  • 1 byte for a decimal point
  • 1 byte for 'e'
  • 1 byte for the sign on the exponent
  • some bytes for the exponent
  • 1 byte for the trailing null written by sprintf.

I'm using the number of significant digits as an upper bound on the number of digits in the exponent. Have I made any errors? Is there a better solution? Should I just allocate 64, 128, or 256 bytes and hope for the best?

+1  A: 

Two things: %g does not show all of the representable digits, %g shows a nice-for-humans rounded result. You can specify the precision using %f or %e if you would like a different result.

Never use sprintf() rather than using snprintf(). In your case: int len = snprintf(buf, dimensionof(buf), "%.*f", DBL_DIG, x);

Heath Hunnicutt
A: 

Should I just round up to 64, 128, or 256 and hope for the best?

Yes, just do that -.-

toto
+4  A: 

Use snprintf() to find out how many characters you need:

#include <float.h> /* DBL_DIG */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  double x = rand() / (double)RAND_MAX;
  char find_len[1];
  int need_len;
  char *buf;

  need_len = snprintf(find_len, 1, "%.*g", DBL_DIG, x);
  buf = malloc(need_len + 1);
  if (buf) {
    int used = sprintf(buf, "%.*g", DBL_DIG, x);
    printf("need: %d; buf:[%s]; used:%d\n", need_len, buf, used);
    free(buf);
  }
  return 0;
}

You need a C99 compiler for snprintf().
snprintf() was defined by the C99 standard. A C89 implementation is not required to have snprintf() defined, and if it has as an extension, it is not required to "work" as described by the C99 Standard.

pmg
Are you sure that you need a C99 compiler for snprintf?
Kinopiko
Consider `#include <stdio.h>` added to `int main(void) { double snprintf = 42; return snprintf - 42; }`. That makes a strictly conforming C89 program, and a required diagnostic for C99. Of course C89 compilers are allowed to provide extensions.
pmg
+4  A: 

You cannot pre-calculate the size at compile time. The %g formatter takes the locale into account (for the 1000's separator etc.) See http://linux.die.net/man/3/sprintf for a description on how to calculate the size safely.

an0nym0usc0ward
Brilliant! I forgot about the locale. Adding an extra DBL_DIG / 3 should cover that, right?
jkl
Not really. For example in India they count in "lakhs" and "crores" with separators for 100 thousand (lakh) and 100*100 thousands = 10 million (crore) etc.
an0nym0usc0ward
Good stuff. So we make the term to cover digit separators a full DBL_DIG. No one could possibly use more than one separator per digit, could they? Are there any other locale-specific variations?
jkl
jkl, I'm not a locale expert, but I learnt that once locales come into play, forget all your assumptions. I'd voe for `snprintf`, too
peterchen
Assuming I don't want separator characters or other locale-specific features in the output, I just temporarily set the locale to 'C' and then set it back when the printing is done. Does that make estimating the buffer size easier?
jkl
A: 

Instead of using sprintf, you could use asprintf. This allocates a buffer of the correct size to fit your string.

Kinopiko
I don't understand why people like snprintf so much. If you are going to use a non-portable function, why not use one like asprintf which gets the job done directly?
jkl