11

I want to format a floating point value to n significant digits but never using scientific notation (even if it would be shorter).

The format specification %f doesn't deal in significant digits, and %g will sometimes give me scientific notation (which is inappropriate for my use).

I want values in the form "123", "12.3", "1.23" or "0.000000123".

Is there an elegant way to do this using C++ or boost?

jabaldonedo
  • 25,822
  • 8
  • 77
  • 77
PeteC
  • 1,047
  • 9
  • 15

3 Answers3

13

The best way I know (and use it in my own code) is

#include <string>
#include <math.h>
#include <sstream>
#include <iomanip>

int round(double number)
{
    return (number >= 0) ? (int)(number + 0.5) : (int)(number - 0.5);
}

std::string format(double f, int n)
{
    if (f == 0) {
        return "0";
    }            
    int d = (int)::ceil(::log10(f < 0 ? -f : f)); /*digits before decimal point*/
    double order = ::pow(10., n - d);
    std::stringstream ss;
    ss << std::fixed << std::setprecision(std::max(n - d, 0)) << round(f * order) / order;
    return ss.str();
}

c++11 has std::round so you won't need my version of with a new compiler.

The trick I'm exploiting here is to get the precision you want by taking the base 10 log to count the number of digits before the decimal and subtracting this from the precision you want.

It satisfies @Mats Petersson's requirement too, so will work in all cases.

The bit I don't like is the initial check for zero (so the log function doesn't blow up). Suggestions for improvement / direct editing of this answer most welcome.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Hardly elegant but closest so far! Thanks. Still prints in S.N. though: `format (0.00000123456, 3)` prints `1.23e-06` – PeteC Jun 20 '13 at 11:59
  • Ok try my amended answer. Pint says it's perfect (but even less elegant now due to precision max). – Bathsheba Jun 20 '13 at 12:17
  • Looks good, thanks a lot. The whole issue is frustrating as the "significant digits" logic is clearly present in the standard library, but it's inseparable from the S.N. aspect. – PeteC Jun 20 '13 at 12:28
  • That is elegant enough, from my point of view :) Thanks for that. – Gauthier Boaglio Jun 20 '13 at 12:36
  • 2
    Here’s one issue (which I will attempt to fix myself): if _f_ is a power of ten and _n_ > log10 _f_, then the output will have an extra zero at the end. For example `format(100.0, 3)` is “100.0”, while it should be “100” (or ideally “100.”, with a trailing decimal point). – bdesham Nov 25 '14 at 22:21
  • 1
    In answer to the issue pointed out by @bdesham, using `floor(..) + 1` instead of `ceil(..)` correctly formats powers of ten. – Alexis Leclerc Apr 03 '17 at 19:02
0

std::fixed and std::setprecision (and <iomanip> in general) are your friends.

std::cout << 0.000000123 << '\n';

prints 1.23e-07 and

std::cout << std::setprecision(15) << std::fixed << 0.000000123 << '\n';

prints 0.000000123000000

Just remember that floating-point numbers have limited precision, so

std::cout << std::fixed << 123456789012345678901234567890.0 << '\n';

will print 123456789012345677877719597056.000000 (probably not what you want)

Massa
  • 8,647
  • 2
  • 25
  • 26
  • 4
    neither of those are what I want. "1.23e-07" is s.n., which I said I wanted to avoid. "0.000000123000000" is a fixed number of decimal points, whereas I said a wanted a fixed number of significant digits. – PeteC Jun 20 '13 at 10:33
0

I think you'll have to remove trailing zeros by yourself :

string trimString(string str)
{
    string::size_type s;
    for(s=str.length()-1; s>0; --s)
    {
        if(str[s] == '0') str.erase(s,1);
        else break;
    }
    if(str[s] == '.') str.erase(s,1);
    return str;
}

Usage :

double num = 0.000000123;
stringstream ss;
ss << num;
ss.str("");
ss << std::setprecision(15) << std::fixed << num; // outputs 0.000000123000000
string str;
ss >> str;
str = trimString(str);
cout << str << endl;  // outputs 0.000000123

Put together :

string format(int prec, double d) {
    stringstream ss;
    ss << d;
    ss.str("");
    ss << std::setprecision(prec) << std::fixed << d;

    string str;
    ss >> str;
    string::size_type s;
    for(s=str.length() - 1; s > 0; --s)
    {
        if(str[s] == '0') str.erase(s,1);
        else break;
    }
    if(str[s] == '.') str.erase(s,1);
    return str;
}

Usage :

double num = 0.000000123;
cout << format(15, num) << std::endl;

If someone knows a better way ...

Gauthier Boaglio
  • 10,054
  • 5
  • 48
  • 85