sprintf >> atof question

T

tjay

Hi. I wrote some code using sprintf and atof to store a double as a
string of fixed length and to convert it back to a double variable. The
string is stored in a char buffer global variable. I'm afraid it might
contain bugs though.

If I serialize a double, I get a string of the format
"-1.0000000000000000e+212". This string gets stored in the buffer.
Then, to convert it back into a double, I pass it to the atof function.

The problem I'm afraid of, is when i serialize
"-1.0000000000000000e+123", then serialize "-1.0000000000000000e+12",
the last character in the buffer should still contain its old value,
'3'. Then when I pass it to atof, it will return
"-1.0000000000000000e+123" (the 3 at the end is not supposed to be
there).

Surprisingly, this doesn't happen when I test my code, so I was
wondering if it was due to glibc doing some magical error checking, or
there's some subtle semantics with sprintf and atof the I don't know
about.


/* Start of code ======================================= */

#include <math.h>

/* 1 sign + 1 decimal + 1 decimal point + 16 fractional
* + 5 exponent = 24 */
char buffer[24];

int main () {
/* "-1.0000000000000000+e307" */
sprintf (buffer, "% .16e", -1 * pow (1, 307));

/* "-1.0000000000000000+e30" */
sprintf (buffer, "% .16e", -1 * pow (1, 30));

/* will this give me "-1.0000000000000000+e307" or
* "-1.0000000000000000+e30"? */
printf (atof (buffer));

return 0;
}

/* End of code ======================================== */


Also, Is there a way to force the exponent to always be 3 characters
long?

TJ
 
A

Allan M. Bruce

tjay said:
Hi. I wrote some code using sprintf and atof to store a double as a
string of fixed length and to convert it back to a double variable. The
string is stored in a char buffer global variable. I'm afraid it might
contain bugs though.

If I serialize a double, I get a string of the format
"-1.0000000000000000e+212". This string gets stored in the buffer.
Then, to convert it back into a double, I pass it to the atof function.

The problem I'm afraid of, is when i serialize
"-1.0000000000000000e+123", then serialize "-1.0000000000000000e+12",
the last character in the buffer should still contain its old value,
'3'. Then when I pass it to atof, it will return
"-1.0000000000000000e+123" (the 3 at the end is not supposed to be
there).

Surprisingly, this doesn't happen when I test my code, so I was
wondering if it was due to glibc doing some magical error checking, or
there's some subtle semantics with sprintf and atof the I don't know
about.


/* Start of code ======================================= */

#include <math.h>

/* 1 sign + 1 decimal + 1 decimal point + 16 fractional
* + 5 exponent = 24 */
char buffer[24];

int main () {
/* "-1.0000000000000000+e307" */
sprintf (buffer, "% .16e", -1 * pow (1, 307));

This will not be -1.00000000000000+e307 since pow (1,307) = 1!
/* "-1.0000000000000000+e30" */
sprintf (buffer, "% .16e", -1 * pow (1, 30));

/* will this give me "-1.0000000000000000+e307" or
* "-1.0000000000000000+e30"? */
printf (atof (buffer));

This wont work - have you given us a program which you have compiled
yourself? Anyway, you should look at sscanf which will convert your strings
to numbers and give you some error checking in the process.
Allan
 
T

tjay

Allan said:
This wont work - have you given us a program which you have compiled
yourself? Anyway, you should look at sscanf which will convert your strings
to numbers and give you some error checking in the process.
Allan

I'm sorry, I wrote that program just to make it clearer what the
problem I was worried about was. Should have tried compiling it before
posting.

I went through the code I actually use to test this problem, and
discovered that sprintf appends a NULL to the end of the char* it is
given. That's why the 3rd exponent character never messed up my smaller
numbers.

Illustration -
value in string: -1.0000000000000000+e123
value in string: -1.2345678900000000+e10NULL

Which means that, given a number which serializes to a 24-character
long string, my buffer must have overflowed. Am I right?

/* Start of code ==============================*/
/* Remember to compile with -lm */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define CONTRAST(x) (printf ("Original : "), \
printf (fmtString, (x)), \
printf ("\nStored : "), \
sprintf (string, fmtString, (x)), \
printf (string), \
printf ("\nRetrieved : "), \
\
printf (fmtString, atof(string)), \
printf ("\n"))

#define STRING_LEN 24
/* 1 sign + 1 decimal + 1 decimal point
* + 16 fractionals + 5 exponent = 24 */

int main (int argc, char **argv) {
double one, two, three, four;
char string[STRING_LEN];
char *fmtString;
int i;

one = -1 * pow (10, 304);
two = -1 * pow (10, 20);
three = -1 * pow (10, 304);
four = -1 * pow (10, 309); /* inf */

fmtString = "% .16e";

/* --- start testing ------------------------------*/

/* Fill up all 3 exponent characters. */
CONTRAST (one);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* This has only 2 exponent characters.
* Will the third exp character from
* before appear here? */
CONTRAST (two);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* Fill up all 3 exponent characters. */
CONTRAST (three);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* This shows up on my pc as "-inf" with
* printf. Simulated write() shows me that
* the string actually contains garbage
* characters from the previous operation. */
CONTRAST (four);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* NOTE: Just discovered that the sim write
* prints 1 less character than the string's
* length for the previous operation. Does
* sprintf append a NULL to the string? If
* so, 'string' is overflowing! */

if (string[4] == 0) {
printf ("**NOTE: NULL appended after \"-inf\"! -- sprintf?\n");
}

return 0;
}
 
A

Allan M. Bruce

I'm sorry, I wrote that program just to make it clearer what the
problem I was worried about was. Should have tried compiling it before
posting.

I went through the code I actually use to test this problem, and
discovered that sprintf appends a NULL to the end of the char* it is
given. That's why the 3rd exponent character never messed up my smaller
numbers.

sprintf uses the standard string convention of appending '\0' - see my
comment below...
Illustration -
value in string: -1.0000000000000000+e123
value in string: -1.2345678900000000+e10NULL

Which means that, given a number which serializes to a 24-character
long string, my buffer must have overflowed. Am I right?

Yes, and by doing so you have entered the realm of undefined behaviour
/* Start of code ==============================*/
/* Remember to compile with -lm */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define CONTRAST(x) (printf ("Original : "), \
printf (fmtString, (x)), \
printf ("\nStored : "), \
sprintf (string, fmtString, (x)), \
printf (string), \
printf ("\nRetrieved : "), \
\
printf (fmtString, atof(string)), \
printf ("\n"))

You are still using atof() - consider using sscanf instead.
#define STRING_LEN 24
/* 1 sign + 1 decimal + 1 decimal point
* + 16 fractionals + 5 exponent = 24 */

BANG! If you use all of these values to the maximum you will run into
problems. The strXXX functions expect valid strings which must have the
null termination character '\0' appended, therefore you must allow space for
this too. Simply add 1 to your STRING_LEN to allow for this. Incidentally
a good book or tutorial should explain this - if it doesnt, get a better
one!
int main (int argc, char **argv) {
double one, two, three, four;
char string[STRING_LEN];
char *fmtString;
int i;

one = -1 * pow (10, 304);
two = -1 * pow (10, 20);
three = -1 * pow (10, 304);
four = -1 * pow (10, 309); /* inf */

fmtString = "% .16e";

You can do this initialisation when defined fmtString with either:
char *fmtString = "%.16e";
char fmtString[] = "%.16e";
/* --- start testing ------------------------------*/

/* Fill up all 3 exponent characters. */
CONTRAST (one);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");


If you do as I suggested above with the null termination character, you can
replace this for loop simply with
printf("%s\n\n", string);
/* This has only 2 exponent characters.
* Will the third exp character from
* before appear here? */
CONTRAST (two);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* Fill up all 3 exponent characters. */
CONTRAST (three);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* This shows up on my pc as "-inf" with
* printf. Simulated write() shows me that
* the string actually contains garbage
* characters from the previous operation. */
CONTRAST (four);
printf("Simulated write() : ");
for (i = 0; i < 24; i++) {
printf ("%c", string);
}
printf ("\n\n");

/* NOTE: Just discovered that the sim write
* prints 1 less character than the string's
* length for the previous operation. Does
* sprintf append a NULL to the string? If
* so, 'string' is overflowing! */
YES!


if (string[4] == 0) {
printf ("**NOTE: NULL appended after \"-inf\"! -- sprintf?\n");
}

return 0;
}


I have changed your code a little to that below. Everything seems to work
apart from when using sscanf to read back
-1.#INF000000000000e+000
The return value is -1.00000000000 as it sees the # and thinks that this is
the end of the number. But otherwise everything seems to work.
Allan

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* 1 sign + 1 decimal + 1 decimal point
* + 16 fractionals + 5 exponent + '\0' = 25 */
#define STRING_LEN 25

void contrast(double x)
{
char string[STRING_LEN];
double a;

printf ("Original\t: %.16e\n", x);
printf ("Stored\t\t: %.16e\n", x);
sprintf(string, "%.16e", x);
printf ("Converted\t: %s\n", string);
if (sscanf (string, "%le", &a) != 1)
{
printf("Failure reading double value, setting to 0\n");
a = 0;
}
printf ("Retrieved\t: %.16e\n\n", a);
}

int main (void)
{
double one = -1 * pow (10, 304),
two = -1 * pow (10, 20),
three = -1 * pow (10, 304),
four =-1 * pow (10, 309);

contrast(one);
contrast(two);
contrast(three);
contrast(four);

return 0;
}
 
T

tjay

Allan said:
BANG! If you use all of these values to the maximum you will run into
problems. The strXXX functions expect valid strings which must have the
null termination character '\0' appended, therefore you must allow space for
this too. Simply add 1 to your STRING_LEN to allow for this. Incidentally
a good book or tutorial should explain this - if it doesnt, get a better
one!

I keep forgetting that "a string" really means "a string\0"...
int main (int argc, char **argv) {
double one, two, three, four;
char string[STRING_LEN];
char *fmtString;
int i;

one = -1 * pow (10, 304);
two = -1 * pow (10, 20);
three = -1 * pow (10, 304);
four = -1 * pow (10, 309); /* inf */

fmtString = "% .16e";

You can do this initialisation when defined fmtString with either:
char *fmtString = "%.16e";
char fmtString[] = "%.16e";

I just like to keep declarations, initializations, and statements all
separate :p
I have changed your code a little to that below. Everything seems to work
apart from when using sscanf to read back
-1.#INF000000000000e+000
The return value is -1.00000000000 as it sees the # and thinks that this is
the end of the number. But otherwise everything seems to work.
Allan

Just out of curiosity, is that how infinity shows up on your system?
Mine just prints -inf or +inf. I'm on Linux with gcc 3.4.4, are you on
a different operating system?

Thanks a lot. I understand now. Cheers :)

TJ
 
A

Allan M. Bruce

tjay said:
I keep forgetting that "a string" really means "a string\0"...

technically "a string" means {'a',' ','s','t','r','i','n','g',\0'} but I
knew what you mean ;-)
int main (int argc, char **argv) {
double one, two, three, four;
char string[STRING_LEN];
char *fmtString;
int i;

one = -1 * pow (10, 304);
two = -1 * pow (10, 20);
three = -1 * pow (10, 304);
four = -1 * pow (10, 309); /* inf */

fmtString = "% .16e";

You can do this initialisation when defined fmtString with either:
char *fmtString = "%.16e";
char fmtString[] = "%.16e";

I just like to keep declarations, initializations, and statements all
separate :p
I have changed your code a little to that below. Everything seems to
work
apart from when using sscanf to read back
-1.#INF000000000000e+000
The return value is -1.00000000000 as it sees the # and thinks that this
is
the end of the number. But otherwise everything seems to work.
Allan

Just out of curiosity, is that how infinity shows up on your system?
Mine just prints -inf or +inf. I'm on Linux with gcc 3.4.4, are you on
a different operating system?

Yes it does, it is implementation defined so Im not sure how you would go
about checking for that problem without a specific check for your double
equalling positive or negative infinity.

Allan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top