views:

249

answers:

5

I have three sets of numbers, a measurement (which is in the range 0-1 inclusive) two errors (positive and negative. These numbers should be displayed consistently to the number of significant figures, rounded up, which corresponds to the first non-zero entry in either of the number.

This requirement is skipped on the measurement if it is one (i.e. only the figures in the errors need be considered). For example:

0.95637 (+0.00123, -0.02935) --> 0.96 +0.00 -0.03
1.00000 (+0.0, -0.0979) --> 1.0 +0.0 -0.1 (note had to truncate due to -ve error rounding up at first significant digit)

Now, getting at the first non-zero digit is easy by taking log10(num), but I'm having an idiotic moment trying to get stripping and rounding working in a clean fashion.

All data types are doubles, and language of choice is C++. All and any ideas welcome!

A: 

Maybe something like:

std::string FormatNum(double num)
{
  int numToDisplay ((int)((num + 0.005) * 100.0));
  stringstream ss;
  int digitsToDisplay(abs(numToDisplay) % 100);
  ss << ((num > 0) ? '+' : '-') << (abs(numToDisplay) / 100) << '.' << (digitsToDisplay / 10) << (digitsToDisplay % 10);
  return ss.str();
}

    stringstream ss;
    ss << FormatNum(0.95637) << ' ' << FormatNum(+0.00123) << ' ' << FormatNum(-0.02935);
Shane Powell
Not only doesn't this solution answer the full question, but it has a bug - what happens to the leading zeroes on the % 100 term?
Mark Ransom
Good catch on the bug.
Shane Powell
ss<< FormatNum(+0.0000134); gives incorrect results
LeBleu
A: 

I'm not quite sure how you're log10 will help you get the first non-zero digit, but assuming it does (and thus you know what decimal place you are rounding to), the following function will round correctly:

double round(double num, int decimalPlaces)
{
    //given your example of .95637 being rounded to two decimal places
    double decimalMultiplier = pow(10, decimalPlaces); // = 100
    double roundedShiftedNum = num * decimalMultiplier + 0.5; // = 96.137
    double insignificantDigits = (roundedShiftedNum - (int)roundedShiftedNum; // = 0.137
    return (roundedShiftedNum - insignificantDigits) / decimalMultiplier; // = (96.137 - 0.137)/100 = 0.96
}

This may not be the most elegant solution, but I believe it works (haven't tried it though)

+2  A: 

Using

cout.setf(ios::fixed, ios::floatfield);
cout.precision(2);

before you output the numbers should do what you are looking for.

Edit: as an example

double a = 0.95637;
double b = 0.00123;
double c = -0.02935;

cout.setf(ios::fixed, ios::floatfield);
cout.precision(2);
cout << a << endl;
cout << b << endl;
cout << c << endl;

will output:

0.96
0.00
-0.03

Further edit: you'll obviously have to adjust the precision to match your significant figures.

Niki Yoshiuchi
A: 

Here's a variation on the version provided by Shane Powell.

std::string FormatNum(double num, int decimals)
{
    stringstream ss;
    if (num >= 0.0)
        ss << '+';
    ss << setiosflags(ios::fixed) << setprecision(decimals) << num;
    return ss.str();
}
Mark Ransom
+2  A: 

My C++ is rusty, but wouldn't the following do it:

std::string FormatNum(double measurement, double poserror, double negerror)
{
  int precision = 1;  // Precision to use if all numbers are zero

  if (poserror > 0)
    precision = ceil(-1 * log10(poserror));
  if (negerror < 0)
    precision = min(precision, ceil(-1 * log10(abs(negerror))));

  // If you meant the first non-zero in any of the 3 numbers, uncomment this:
  //if( measurement < 1 )
  //  precision = min(precision, ceil(-1 * log10(measurement)));

  stringstream ss;
  ss.setf(ios::fixed, ios::floatfield);
  ss.precision( precision );
  ss << measurement << " +" << poserror << " " << negerror ;
  return ss.str();
}
LeBleu