Question

I am converting double values to string like this:

std::string conv(double x) {
    char buf[30];
    sprintf(buf, "%.20g", x);
    return buf;
}

I have hardcoded the buffer size to 30, but am not sure if this is large enough for all cases.

  • How can I find out the maximum buffer size I need?
  • Does the precision get higher (and therefore buffer needs to increase) when switching from 32bit to 64?

PS: I cannot use ostringstream or boost::lexical_cast for performance reason (see this)

Was it helpful?

Solution

I have hardcoded the buffer size to 30, but am not sure if this is large enough for all cases.

It is. %.20g specifies 20 digits in the mantissa. add 1 for decimal point. 1 for (possible) sign, 5 for "e+308" or "e-308", the worse case exponent. and 1 for terminating null.

20 + 1 + 1 + 5 + 1 = 28.

Does the precision get higher (and therefore buffer needs to increase) when switching from 32bit to 64?

No.

A double is the same size in both architectures. If you declare your variables as long double, then you possibly have 1 more digit in the exponent "e+4092", which still fits in a 30 character buffer. But only on X86, and only on older processors.

The long double is an obsolete 80 bit form of floating point value that was the native format of the 486 FPU. That FPU architecture didn't scale well and as since been discarded in favor of SSE style instructions where the largest possible floating point value is a 64 bit double.

Which is a long way of saying a buffer of 30 characters will always be sufficient as long as you keep limiting the mantissa in your printout to 20 digits.

OTHER TIPS

printf("%.20g", 1.79769e+308); is 1.7976900000000000632e+308, 27 bytes including the trailing \0. I would choose 64 or 128 just to be sure.

(Since it's on the stack and released right after you could also go with big buffers, even 2048 bytes, without running into problems for non embedded applications)

Also, are you sure the bottleneck of your program is lexical_cast..? Doing what you're doing seems very silly to me

I seem to remember that if you call sprintf with a NULL destination, it doesn't do anything. It does, however, return the number of chars that it "wrote". If I'm right (and I can't seem to find the source for that) then you can do:

// find the length of the string
int len = sprintf(NULL, fmt, var1, var2,...);
// allocate the necessary memory.
char *output = malloc(sizeof(char) * (len + 1)); // yes I know that sizeof(char) is defined as 1 but this seems nicer.
// now sprintf it after checking for errors
sprintf(output, fmt, var1, var2,...);

Another option is to use snprintf which allows you to limit the length of the output:

#define MAX 20 /* or whatever length you want */
char output[MAX];
snprintf(output, MAX, fmt, var1, var2,...);

snprintf takes the size of the buffer as an argument, and doesn't allow the output string to exceed that size.

Here's a program to print the number of digits required for maximum and minimum values double can take for any system:

#include <float.h>
#include <stdio.h>

int main(void)
{
    double m = DBL_MAX;
    double n = DBL_MIN;
    int i;
    i = printf("%.20g\n", m);
    printf("%d\n", i);
    i = printf("%.20g\n", n);
    printf("%d\n", i);
    return 0;
}

For me, it prints:

1.7976931348623157081e+308
27
2.2250738585072013831e-308
27

Since 27 includes a newline but doesn't include the terminating 0 for the strings, I would say that on this system, 27 should suffice. For long double, the answer seems to be 27 and 28 for LDBL_MAX and LDBL_MIN respectively on my system.

The man page (on my for sprintf says this about %g:

The double argument is converted in style f or e (or F or E for G conversions). The precision specifies the number of significant digits. If the precision is missing, 6 digits are given; if the precision is zero, it is treated as 1. Style e is used if the exponent from its conversion is less than -4 or greater than or equal to the precision. Trailing zeros are removed from the fractional part of the result; a decimal point appears only if it is followed by at least one digit.

Similar wording is in the C standard.

So I think you will be safe if you used the output from the above program as your array size.

If you are on a platform that supports POSIX or C99, you should be able to use snprintf to compute the size of the buffer you will need. snprintf takes a parameter indicating the size of the buffer you are passing in; if the size of the string would exceed the size of that buffer, it truncates the output to fit into the buffer, and returns the amount of space it would have needed to fit the entire output. You can use the output of this to allocate a buffer that's the exact right size. If you just want to compute the size of the buffer you need, you can pass in NULL as the buffer and a size of 0 to compute how much space you need.

int size = snprintf(NULL, 0, "%.20g", x);
char *buf = malloc(size + 1); // Need the + 1 for a terminating null character
snprintf(buf, size + 1, "%.20g", x);

Remember to free(buf) after you've used it to avoid memory leaks.

The problem with this is that it won't work in Visual Studio, which still does not support C99. While they have something like snprintf, if the buffer passed in is too small, it does not return the size needed, but returns -1 instead, which is completely useless (and it does not accept NULL as a buffer, even with a 0 length).

If you don't mind truncating, you can simply use snprintf with a fixed size buffer, and be assured that you won't overflow it:

char buf[30];
snprintf(buf, sizeof(buf), "%.20g", x);

Make sure you check your platform docs on snprintf; in particular, some platforms may not add a terminating null at the end of the string if the string is truncated, so you may need to do that yourself.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top