atof accuracy with double is causing grief

12,731

Solution 1

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

Solution 2

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.68128592893...  previous double
"15605632.68128593"
 15605632.68128593080...  closest double

The grief comes when attempting to print, thinking that what printed was the exact value of x. Instead a rounded value was printed. 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. 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_DIG is 15 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_DIG + 2. although DBL_DIG may be sufficient. 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

Solution 3

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.

Share:
12,731
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin over 1 year

    I have an ascii "15605632.68128593" and i wish to convert it to a double without losing accurary -

     so 
     double d;
     d=(double)atof("15605632.68128593");
     printf("%f",d);
    
     printed result is 15605632.681286
    

    Any ideas ?

    Thanks - !