Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Print all significant digits in sprintf scientific notation

Tags:

c

r

printf

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?

like image 747
Jeroen Ooms Avatar asked Oct 03 '14 17:10

Jeroen Ooms


People also ask

What is the precision specifier number of significant digits?

%g and number of significant digits When using %g, the precision determines the number of significant digits. The default precision is 6.

How do you change a number into a significant figure?

We round a number to three significant figures in the same way that we would round to three decimal places. We count from the first non-zero digit for three digits. We then round the last digit. We fill in any remaining places to the right of the decimal point with zeros.

Are trailing zeros significant?

Zeros between non zero digits are significant. Zeros to the left of the first non zero digit are not significant. more easily seen if it is written as 3.4x10-5) ~ 0.001111 has four significant figures. Trailing zeros (the right most zeros) are significant when there is a decimal point in the number.


1 Answers

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
like image 52
chux - Reinstate Monica Avatar answered Oct 21 '22 04:10

chux - Reinstate Monica