2

I've written a program that calculates values in a series and all of the values are particularly lengthy doubles. I want to print these values each displaying 15 significant figures. Here's some code that illustrates the issue I'm having:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double x = 0.12345678901234567890;
    double y = 1.12345678901234567890;
    cout << setprecision(15) << fixed << x << "\t" << y << "\n";
    return 0;
}

With just setprecision trailing zeros are not shown so I added fixed as I have seen in other answers on this site. However, now I just seem to have 15 decimal places and for values that aren't 0.something this is not what I want. You can see this from the output of the above:

0.123456789012346       1.123456789012346

The first number has 15 sig figs but the second has 16. What can I do to resolve this?

EDIT: I have been specifically asked to use setprecision, so I am unable to try cout.precision.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
treeno
  • 39
  • 1
  • 4

2 Answers2

3

You can simply use scientific (note the 14 instead of 15):

std::cout << std::scientific << std::setprecision(14) << -0.123456789012345678 << std::endl;
std::cout << std::scientific << std::setprecision(14) << -1.234567890123456789 << std::endl;

-1.23456789012346e-01
-1.23456789012346e+00

or you can use a function:

#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include <sstream>

enum vis_opt { scientific, decimal, decimal_relaxed };

std::string figures(double x, int nfig, vis_opt vo=decimal) {
    std::stringstream str;

    str << std::setprecision(nfig-1) << std::scientific << x;
    std::string s = str.str();
    if ( vo == scientific )
        return s;
    else {
        std::stringstream out;
        std::size_t pos;

        int ileft = std::stoi(s,&pos);

        std::string dec = s.substr(pos + 1, nfig - 1);
        int e = std::stoi(s.substr(pos + nfig + 1));

        if ( e < 0 ) {
            std::string zeroes(-1-e,'0');
            if ( ileft < 0 ) 
                out << "-0." << zeroes << -ileft << dec;
            else
                out << "0." << zeroes << ileft << dec;
        } else if ( e == 0) {
            out << ileft << '.' << dec;
        } else if ( e < ( nfig - 1) ) {
            out << ileft << dec.substr(0,e) << '.' << dec.substr(e);
        } else if ( e == ( nfig - 1) ) {
            out << ileft << dec;
        } else {
            if ( vo == decimal_relaxed) {
                out << s;
            } else {
                out << ileft << dec << std::string(e - nfig + 1,'0');
            }
        }

        return out.str();   
    }

}

int main() {
    std::vector<double> test_cases = {
        -123456789012345,
        -12.34567890123456789,
        -0.1234567890123456789,
        -0.0001234,
        0,
        0.0001234,
        0.1234567890123456789,
        12.34567890123456789,
        1.234567890123456789,
        12345678901234,
        123456789012345,
        1234567890123456789.0,
    };


    for ( auto i : test_cases) {
        std::cout << std::setw(22) << std::right << figures(i,15,scientific);
        std::cout << std::setw(22) << std::right << figures(i,15) << std::endl;
    }
    return 0;
}

My output is:

 -1.23456789012345e+14      -123456789012345
 -1.23456789012346e+01     -12.3456789012346
 -1.23456789012346e-01    -0.123456789012346
 -1.23400000000000e-04 -0.000123400000000000
  0.00000000000000e+00      0.00000000000000
  1.23400000000000e-04  0.000123400000000000
  1.23456789012346e-01     0.123456789012346
  1.23456789012346e+01      12.3456789012346
  1.23456789012346e+00      1.23456789012346
  1.23456789012340e+13      12345678901234.0
  1.23456789012345e+14       123456789012345
  1.23456789012346e+18   1234567890123460000
gsamaras
  • 71,951
  • 46
  • 188
  • 305
Bob__
  • 12,361
  • 3
  • 28
  • 42
1

I've found some success in just computing the integer significant figures, and then setting the floating significant figures to be X - <integer sig figs>:

Edit

To address Bob's comments, I'll account for more edge cases. I've refactored the code somewhat to adjust the field precision based on leading and trailing zeros. There would still be an edge case I believe for very small values (like std::numeric_limits<double>::epsilon:

int AdjustPrecision(int desiredPrecision, double _in)
{
    // case of all zeros
    if (_in == 0.0)
        return desiredPrecision;
        
    
    // handle leading zeros before decimal place
    size_t truncated = static_cast<size_t>(_in);
    
    while(truncated != 0)
    {
        truncated /= 10;
        --desiredPrecision;
    }
    
    // handle trailing zeros after decimal place
    _in *= 10;
    while(static_cast<size_t>(_in) == 0)
    {
        _in *= 10;
        ++desiredPrecision;
    }
        
    return desiredPrecision;
}

With more tests:

double a = 0.000123456789012345;
double b = 123456789012345;
double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
double z = 11.12345678901234567890;


std::cout.setf( std::ios::fixed, std:: ios::floatfield);

std::cout << "a: " << std::setprecision(AdjustPrecision(15, a)) << a << std::endl;
std::cout << "b: " << std::setprecision(AdjustPrecision(15, b)) << b << std::endl;
std::cout << "x " << std::setprecision(AdjustPrecision(15, x))  << x << std::endl; 
std::cout << "y " << std::setprecision(AdjustPrecision(15, y))  << y << std::endl; 
std::cout << "z: " << std::setprecision(AdjustPrecision(15, z)) << z << std::endl;

Output:

a: 0.000123456789012345
b: 123456789012345
x 0.123456789012346
y 1.12345678901235
z: 11.1234567890123

Live Demo

int GetIntegerSigFigs(double _in)
{
    int toReturn = 0;
    int truncated = static_cast<int>(_in);

    while(truncated != 0)
    {
        truncated /= 10;
        ++toReturn;
    }
    return toReturn;
}

(I'm sure there are some edge cases I'm missing)

And then using it:

double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
std::cout << td::setprecision(15-GetIntegerSigFigs(x))  << x 
<< "\t" << std::setprecision(15-GetIntegerSigFigs(y)) << y << "\n";

Prints:

0.123456789012346 1.12345678901235

Live Demo

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • 1
    try 0.000123456789012345 or 123456789012345 – Bob__ Nov 12 '15 at 17:40
  • @Bob__ Thanks for pointing that out. Overflow on int, and trailing zeros after decimal were unaccounted for in my answer. – AndyG Nov 12 '15 at 18:31
  • @Bob__: I've attempted to address some of these inadequacies in my latest edit. Please feel free to find more :-) The general point I'm trying to make is that, sans a library, there is no way to do what OP wants nicely with standard libraries, so they must compute it themselves. – AndyG Nov 12 '15 at 18:54
  • I agree, as far as I know. – Bob__ Nov 12 '15 at 19:20