user967007
user967007

Reputation:

atof accuracy with double is causing grief

I have an ascii "15605632.68128593" and I wish to convert it to a double without losing accuracy

double d;
d=(double)atof("15605632.68128593");
printf("%f",d);

printed result is 15605632.681286

Any ideas?

Upvotes: 3

Views: 14866

Answers (3)

chux
chux

Reputation: 154582

Goal: Convert "15605632.68128593" to a double without losing accuracy.

atof() accomplished that to best the program could do. But since "15605632.68128593" (a 16-digit number) is not exactly representable as a double in your C, it was approximated to 1.560563268128593080...e+07. Thus accuracy was lost, albeit a small loss.

Typical double can represent about 264 different numbers. The nearby candidates and OP's string are shown below for reference.

 15605632.68128 592893...  previous double
"15605632.68128 593"       code's string 
 15605632.68128 593080...  closest double
 15605632.68128 6          output

The grief comes when attempting to print, thinking that what printed was the exact value of x. Instead the nearby double value was printed. Printout is also rounded. Using the %f specifier defaults to 6 places to the right of the '.' giving the reported 15605632.681286, a 14 digit number.

A better way to see all the significant digits for all double is to use the %e format with DBL_DIG or DBL_DECIMAL_DIG. DBL_DIG is the most number of digits to the right of the '.', in decimal exponential notation %e, to show all the digits needed to "round-trip" a double (string to double to string without a string difference). Since %e always shows 1 digit to the left of '.', the print below shows 1 + DBL_DIG significant digits. DBL_DECIMAL_DIG is 17 on my mine and many C environments, but it vary.

If you wish to show all the significant digits, you need to qualify what is significant. The nextafter() function shows the next representable double. So we might want to show at least enough digits to distinguish x and the next x. I recommend DBL_DECIMAL_DIG. Details

The exact value the program used for your "1.560563268128593e+07" is 15605632.68128593079745769500732421875. There are few situations where you need to see all those digits. Even is you request lots of digits, at some point, printf() just gives you zeros.

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

int main(int argc, char *argv[]) {
  double x;
  x = atof("15605632.68128593");
  printf("%.*le\n",DBL_DIG, x); // All digits "round-trip" string-to-double-string w/o loss
  printf("%.*le\n",DBL_DIG + 1, x);  // All the significant digit "one-way" double-string
  printf("%.*le\n",DBL_DIG + 1, nextafter(x, 2*x)); // The next representable double
  printf("%.*le\n",DBL_DIG + 3, x);  // What happens with a few more
  printf("%.*le\n",DBL_DIG + 30, x); // What happens if you are a bit loony
  return 0;
}

1.560563268128593e+07
1.5605632681285931e+07
1.5605632681285933e+07
1.560563268128593080e+07
1.560563268128593079745769500732421875000000000e+07

Upvotes: 6

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215617

double does not have that much precision. It can only round-trip 15 (DBL_DIG from float.h) decimal places from decimal string to double back to decimal string.

Edit: While, in general, my claim is true, it doesn't seem to be your problem here. While there exist 16-decimal-place numbers which can't be round-tripped, this particular input can.

Upvotes: 1

Gene
Gene

Reputation: 47020

It's likely you're not getting all the trailing decimal places. Try printf("%.8f", d).

You might also try sscanf("15605632.68128593", "%lf", &d) in place of the atof call.

It's also not necessary to cast the result of atof to double. It's already a double. But the cast does no harm.

Note that - at least about 6 years ago when I looked at this in detail - many printf and scanf implementations were buggy in the sense that they didn't function as perfect inverses as you'd assume. Visual C/C++ and gcc both had problems in their native implementations. This paper is a useful reference.

Cygwin with gcc 4.3.4:

#include <stdio.h>

int main(void)
{
  double x;

  sscanf("15605632.68128593", "%lf", &x);
  printf("%.8f\n", x);
  return 0;
}

And then:

# gcc foo.c
# ./a
15605632.68128593

Upvotes: 7

Related Questions