3

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
    Is there a reason that you can only use such a precise amount of memory? – Ed S. Nov 03 '09 at 00:36
  • 2
    I'm not sure the answer, but one thing I want to advise against is using sprintf. You should always use snprintf so that if your calculation of minimum buffer size is wrong, you won't overflow the buffer: "snprintf(buf, sizeof(buf), ...);" – R Samuel Klatchko Nov 03 '09 at 00:53

5 Answers5

5

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
  • 106,608
  • 13
  • 126
  • 198
  • Are you sure that you need a C99 compiler for snprintf? –  Nov 03 '09 at 02:59
  • Consider `#include ` 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 Nov 03 '09 at 08:59
5

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
  • 1,207
  • 8
  • 8
  • Brilliant! I forgot about the locale. Adding an extra DBL_DIG / 3 should cover that, right? –  Nov 03 '09 at 02:16
  • 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 Nov 03 '09 at 11:49
  • 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? –  Nov 03 '09 at 16:57
  • 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 Nov 03 '09 at 17:12
  • 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? –  Nov 03 '09 at 17:13
1

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
  • 18,667
  • 3
  • 39
  • 62
1

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

  • 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? –  Nov 03 '09 at 18:17
-2

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

Yes, just do that -.-

toto
  • 880
  • 11
  • 21