2

Say I have a couple of values with uncertainty estimates

double x, sigma_x;

e.g.

45.34302958634   ± 4.25976343
3.52986798343    ± 0.2363467
3.3734874533e+12 ± 6.34659e+6

Clearly, most of those decimals aren't significant. How do I choose the correct number of “significant digits” (what does that even mean?), to always get as many decimals as needed out of printf, but no more?

I.e. I want some definition of char* fmtString, dependent on sigma_x, such that

printf(fmString, x)

yields

45
3.5
3.373487e+12
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 1
    Read the `printf` documentation. It says it all. – Paul Ogilvie Nov 12 '15 at 11:29
  • I think this post is very related to your question: http://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value – terence hill Nov 12 '15 at 11:41
  • @PaulOgilvie: well, not really. The documentation says something about number of significant digits and so on, but not what this means for the actual uncertainty produced in showing the values. – leftaroundabout Nov 12 '15 at 11:54
  • The documentation also says something about `*` in the format meaning that the value (e.g. width) is provided as a parameter. – Paul Ogilvie Nov 12 '15 at 12:12
  • The answer is you can't do that with just `printf()` it is not that smart for your very specific requirements. It will require manipulation of the value before printing. – Clifford Nov 12 '15 at 15:53

4 Answers4

1

The code below outputs the following for the test data:

45
3.5
3.373487e+12

Which is exactly as required, and moreover does not suffer from the flaws in my original solution, as well as being much simpler.

floor( log10(sigma) ) determines the position of the last significant digit in relation to the decimal point. The rest of the expression essentially zeroes the non-significant digits. The value thus adjusted, it can be printed using supported floating-point format specifiers as required.


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

double fn( double x, double sigma )
{
    return x - fmod( x, pow( 10, floor( log10(sigma) ) ) );
}       

int main()
{

    struct
    {
        double x ;
        double sigma ;
    } data[] = {{45.34302958634, 4.25976343},
                {3.52986798343, 0.2363467},
                {3.3734874533e+12, 6.34659e+6}} ;

    for( int i = 0; i < sizeof(data)/sizeof(*data); i++ )
    {
        double xs = fn( data[i].x, data[i].sigma ) ;
        printf( "%.7g\n", xs ) ;
    }

    return 0 ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • This approach has surprising output when the +sigma causes a carry into a new digit. Example: `{99.0, 2}` prints `90`. – chux - Reinstate Monica Nov 14 '15 at 17:59
  • @chux : Actually, the approach has no merit. It happens to work for the test examples given. I'd delete it, except it is not possible to delete the accepted answer. – Clifford Nov 14 '15 at 22:15
  • @chux : Reimplemented using a mathematical approach. Much better I think. – Clifford Nov 14 '15 at 23:11
  • @leftaroundabout : I have significantly modified this answer as the original was seriously flawed. It is now the mathematical solution I originally suggested was possible. However you may wish to reconsider whether the answer is acceptable. – Clifford Nov 14 '15 at 23:21
  • Post-processing floating-point to string output has many challenges and corner cases that are difficult to solve. Admitting an accepted answer is deficient and coming up with a much better approach in itself deserves an up-vote. – chux - Reinstate Monica Nov 15 '15 at 00:00
  • Deleting per request of poster. – Brad Larson Nov 21 '15 at 21:48
1

The question isn't much clear to me about how to format the output. But as I get it,

printf("%.*g", precision, number);

seems good enough.

So, How to calculate precision?

This is not the fastest way surely. But you can do it as you would do on the paper.

int calc_width(double n, double d)
{
    char s[300], e[300]; //300?? I don't know how much to use. ;)
    int i, j = 0;

    //write them down
    sprintf(s, "%f", n-d/100);
    sprintf(e, "%f", n+d/100);

    //see how many are same
    for(i=0; s[i] == e[i]; i++)
        if(s[i] == '.') j = 1; //well, see 'man printf'

    return i-j;
}

Test:

int main()
{
    double n[] = {45.34302958634, 3.52986798343, 3.3734874533e+12};
    double d[] = {4.25976343, 0.2363467, 6.34659e+6};
    int i;

    for(i=0; i<3; i++)
        printf("%.*g\n", calc_width(n[i], d[i]), n[i]);

    return 0;
}

Result:

45.3
3.5
3.373487e+12
Shakil Ahamed
  • 587
  • 3
  • 18
0

Use this:

printf("%.*g", precision, number);

with precision computed from your sigma_x as you desire.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • 1
    In your formula precision must be an int. How do you compute it from sigma_x? – terence hill Nov 12 '15 at 11:48
  • @terencehill as required for OPs purpose. His description of the precision he wants is vague, so I assume he is able to come up with a formula to compute the precision he wants from his standard derivation. – fuz Nov 12 '15 at 11:50
  • Well, I want the precision to be such that the _difference_ between the displayed value and the supplied value `x` is _at most `sigma_x`_. But I can't see from the definition what this means for `precision`. – leftaroundabout Nov 12 '15 at 11:52
  • @leftaroundabout In this case, something like `log10(x/sigma_x)` should do the trick. – fuz Nov 12 '15 at 11:58
  • 1
    @FUZxxl _something like..._ well, I'd suppose so. The question is, _what exactly_? Everything I've tried so far fails on some input. For instance, if `x = 33734.874533` and `s_x = 6.3`, then `printf("%.*g", round(log10(x/s_x)), x)` gives me `33734.8745`, but none of the decimals are actually significant. I implemented a function that [does all this manually by trimming an intermediate string](https://github.com/leftaroundabout/cqtx/blob/master/phq_base.cpp#L1480), but there has to be a more standard way! – leftaroundabout Nov 12 '15 at 14:24
  • 1
    @leftaroundabout What output did you want for this case? Perhaps forcing scientific notation with `%.*e` is better suited for your purpose as `%g` chooses normal or scientific notation based on the magnitude of the argument. With `%.*e`, the output for your example would be `3.3734e+4` which has as many decimal digits as you want. – fuz Nov 12 '15 at 15:31
  • This does not produce the result @leftaroundabout has requested. It is not in the capability of printf() to do this. It requires mathematical modification of the value first, and it is not printf()'s responsibility to manipulate the value in that way. It is a maths problem rather then an output formatting problem. – Clifford Nov 14 '15 at 23:30
0

Use "%.*e" to control the number of digits after the decimal point.

Include various tests to insure a sane precision level.

Disagree that 45.34302958634 ± 4.25976343 should print as 45 but as 45.3 - but that is a math detail. Adjust the precision calculation as needed.

OP is not clear when to use exponential notation or not. "%.*g" will get code there if it match OP's unstated cut-off.

#include <float.h>
#include <math.h>
// #define DBL_DECIMAL_DIG (DBL_DIG + 3)

void print_number_sigma(double x, double sigma_x) {
  double precision = sigma_x*x ? 
      ceil(log10(fabs(x)) - log10(fabs(sigma_x))) :
      DBL_DECIMAL_DIG - 1;
  if (precision <= 0) precision = 1;
  else if (precision >= DBL_DECIMAL_DIG) precision = DBL_DECIMAL_DIG - 1;
  printf("%.*e +/- %e\n", (int) precision, x, sigma_x);
}

int main(void) {
  print_number_sigma(45.34302958634, 4.25976343);
  print_number_sigma(3.52986798343, 0.2363467);
  print_number_sigma(3.3734874533e+12, 6.34659e+6);
  return 0;
}

Output

4.53e+01 +/- 4.259763e+00
3.53e+00 +/- 2.363467e-01
3.373487e+12 +/- 6.346590e+06
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256