9

When R converts a large number to a string in scientific notation, it includes all significant digits and no trailing zero's. Is it possible to accomplish this in C with sprintf?

> as.character(12345e11)
[1] "1.2345e+15"
> as.character(1234500000e6)
[1] "1.2345e+15"
> as.character(1234500001e6)
[1] "1.234500001e+15"

I tried sprintf(buf, "%g", val) but this seems to include 5 decimal digits at most. I also tried to set a higher precision with sprintf(buf, "%.18g", val) but this will include non significant digits and trailing zeros.

Is there a way to get the behavior of sprintf(buf, "%g", val) but increase the 5 digit limit?

Jeroen Ooms
  • 31,998
  • 35
  • 134
  • 207
  • 3
    Trailing zeros are actually very significant. – 5gon12eder Oct 03 '14 at 17:57
  • I don't think it is possible at least according to [this](https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf) post, which yours likely is a duplicate of. What disturbs me is that the man page of `printf` says for the `%g` format: *“Trailing zeros are removed from the fractional part of the result”* I could not observe this documented behavior. – 5gon12eder Oct 03 '14 at 18:10
  • 1
    @5gon12eder -- How about a carefully contrived number like `n=58.375`? Printing that with `printf("%.18g\n",n)` gives me `58.375` as expected, whereas `n=58.374` results in `58.3740000000000023` Note that .375 is equal to 3/8, so it has an exact floating point representation. Most arbitrarily chosen decimal fractions do **not** have an exact floating point representation. Hence, `%g` doesn't behave the way you might expect for those numbers. – user3386109 Oct 03 '14 at 18:48
  • 1
    @Jeroen -- The examples you give are all large integers. To force the integer to be printed in scientific notation, use `%.16e`. Then parse backwards from the end of the string to find the `e`, and remove any `0`s before the `e`. That's the best you can do. Note that if your number has a fractional part, then there is no solution, as discussed in my previous comment. – user3386109 Oct 03 '14 at 18:57
  • @user3386109 I'm well aware of floating point representation issues but I would not say that “58.3740000000000023” has trailing zeros. Anyway, the OP has integers which ought to have an exact representation. But you are right, I must have done something wrong earlier (code no longer exists). `printf("%.10g\n", n)` (but not with 10 replaced by 18) indeed produces the desired output. However, if you always want scientific notation, I still don't see how we can get it. But at least, `printf` seems to do what it should. Sorry for the false alarm. – 5gon12eder Oct 03 '14 at 19:06
  • possible duplicate of [Printf width specificer to maintain precision of floating-point value](http://stackoverflow.com/questions/16839658/printf-width-specificer-to-maintain-precision-of-floating-point-value) – Degustaf Oct 03 '14 at 20:14

1 Answers1

4

Code could use "%.18e" or "%.18g", but the question is how large should "18" be? Is 18 the best value? The answer lies in DBL_DECIMAL_DIG.

DBL_DECIMAL_DIG is the minimum number of significant digits to print to insure the round-trip of double to string to the same exact double for all double.

Recommend using format specifier "%.*e".

Note that the "18" in "%.18e" is the number of significant digits after the decimal point. So "%.18e" prints 19 significant digits.


Use printf("%a", x); which prints in a hexadecimal output.
For a decimal output:

#include <float.h>

//    sign + digit +  dp +       digits          + e + sign + expo + \0
char buf[1 + 1 +      1  + (DBL_DECIMAL_DIG - 1) + 1 + 1    + 5    + 1]; 
sprintf(buf, "%.*e", DBL_DECIMAL_DIG - 1, x);

Ref Printf width specificer to maintain precision of floating-point value


A number like y = 1.0/3.0 using the typical double binary64 format would need to see about 53 decimal digits to see its exact value. But many of the trailing digits are not needed for a successful round-trip.


Now we know the most digits to print, use the below to get rid of those pesky trailing 0 digits.

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

char *trim0(double x, char *buf) {
  sprintf(buf, "% .*e", DBL_DECIMAL_DIG - 1, x);
  if (isfinite(x)) {
    char *p = &buf[DBL_DECIMAL_DIG + 1];  // address of last significand digit
    char *t;
    for (t=p; *t == '0'; t--);
    memmove(t+1, p+1, strlen(p+1)+1);
  }
  return buf;
}

int main(void) {
  char buf[1 + 1 + 1 + (DBL_DECIMAL_DIG - 1) + 1 + 1 + 5 + 1];
  printf("%s\n", trim0(1.2, buf));
  printf("%s\n", trim0(1.0/7, buf));
  return 0;
}

Output

 1.2e+00
 1.4285714285714285e-01
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256