开发者

Use only standard C++ to print any float to exactly N spaces

开发者 https://www.devze.com 2023-02-26 09:29 出处:网络
I have a float that I would like to print to exactly N spaces. For example, with N = 5. I want to get the following numbers printed as so:

I have a float that I would like to print to exactly N spaces. For example, with N = 5. I want to get the following numbers printed as so:

0.1        => 0.1
123.456789 => 123.5
-123.45678 => -123 (or -123.)
0.123456   => 0.123
123456.789 => 123456 (obviously, in this case, we cannot print less than 6 digits, if I can detec开发者_StackOverflow社区t this, I can also print 12** to indicate the field is too wide.

I have tried many combination including the following:

// Leave 3 extra space: One for negative sign, one for zero, one for decimal
std::cout << std::setiosflags(std::ios::fixed) 
          << std::setprecision(N - 3) 
          << std::setw(N) 
          << input;

But the results are not favorable. Perhaps I am missing something obvious?

The output of the given code for those sample inputs are

 0.10     // not bad 
123.46    // Use 6 instead of 5 spaces
-123.46   // Use 7 instead of 5 spaces
 0.12     // Under-use the spaces
123456.79 // Ok, I guess


Use Boost's Spirit.Karma for this:

#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;

double d = 12345.6;

// will print: 12345
std::cout << karma::format(
        karma::maxwidth(5)[karma::double_], d
    ) << std::endl;

which will limit the width of your output to 5 in any case. If you want to catch the case where the number would be cut off (as in your example for 123456.7), just perform the appropriate check up front:

if (d < 1e6) {
    // d == 12345 will print: 12345
    std::cout << karma::format(
            karma::maxwidth(5)[karma::double_], d
        ) << std::endl;
}
else {
    // d == 123456 will print: 12***
    std::cout << karma::format(
            karma::left_align(5, '*')[
                karma::maxwidth(2)[karma::double_]
            ], d
        ) << std::endl;
}

And yes, in case you might ask, Boost's Spirit.Karma is written using only Standard C++ features.


int main ()
{
  const int N = 4;

  double f = 123.456789;

  // just use default floating-point notation to display N significant digits.
  // no fixed or scientific notation work for your requirement.
  // you can comment setf() line out if there is no one changing ios::floatfield.
  cout.setf(0, ios::floatfield); 
  cout.precision(N);

  cout << f << endl;              // will print 123.5
}

[UPDATE] I should have tested for all cases provided. :D

#include <iostream>
#include <math.h>
using namespace std;

void print_test(double number)
{
    const int N = 5; // should be greater than 2

    double maxIntPart = pow(10.0, number >= 0 ? N - 2 : N - 3);

    double fractpart, intpart;
    fractpart = modf(number , &intpart);

    ios_base::fmtflags prevNotation = cout.setf(0, ios::floatfield);  // use default floating-point notation
    streamsize prevPrecicion = cout.precision();

    if( abs(intpart) > maxIntPart )
    {
        intpart = ceil(number);
        if( abs(intpart) < maxIntPart * 10 )
        {
            cout << intpart << endl;
        }
        else
        {               
            while( abs(intpart) >= maxIntPart )
            {
                intpart /= 10;
            }

            cout << (intpart > 0 ? floor(intpart) : ceil(intpart)) << "**" << endl;
        }
    }
    else
    {
        if ( number < 0 )
        {
            cout.precision(N - 3);
        }
        else if ( intpart == 0)
        {
            cout.precision(N - 2);
        }
        else
        {
            cout.precision(N - 1);
        }
        cout << number << endl;
    }

    cout.setf(prevNotation, ios::floatfield); 
    cout.precision(prevPrecicion);
}


int main ()
{
    print_test(0.1);                // 0.1
    print_test(123.456789);         // 123.5
    print_test(-123.45678);         // -123
    print_test(0.123456);           // 0.123
    print_test(-0.123456);          // -0.12
    print_test(123456.789);         // 123**
    print_test(-123456.789);        // -12**
 }


You seem to want to print N characters - including the minus sign (if any) and decimal point. setprecision only cares about the number of digits after the decimal point. So, a little math can get you what you want:

#include <cmath>

// determine what precision we need
int precision = N - 1;                      // leave room for the decimal point
if (input < 0) --precision;                 // leave room for the minus sign
precision -= (1 + (int)log10(abs(input)));  // how many digits before the decimal?
if (precision < 0) precision = 0;           // don't go negative with precision

std::cout << std::setiosflags(std::ios::fixed) 
          << std::setprecision(precision) 
          << std::setw(N) 
          << input;

I probably need to explain the logarithm line:

precision -= (1 + (int)log10(abs(input))); // how many digits before the decimal?
  • anything from 0-9 takes one digit to print.
  • log10 of 0-9 would give a number less than 1, which would truncate to int 0
  • anything from 10-99 takes two digits to print.
  • log10 of 10-99 would give a number at least 1, but less than 2 - truncates to int 1
  • so... log10(input) plus 1 is how many digits it prints


Your requirements aren't catered for well by the iostream precision/width flags - for example, precision counts significant digits not character positions, and 4 requests 0.123456 be mapped to 0.1234 not the 0.123 you request (would you expect 0.00123456 to be "0.00"?). Anyway, to use precision you'd need to vary the parameter depending on the value. It may be easier to write your own function as below...

The easiest way is to write the value to a stringstream with extra precision, then scan left-to-right (recording whether you find a '.') until you reach the point where you'd like to truncate, then scanning right-to-left to remove trailing 0s or to substitute "**" if the "." wasn't encountered earlier.

Otherwise, you can implement your own double-to-ASCII conversion, possibly using a header like linux's <ieee754.h> to provide a union with bit fields over float and double types; otherwise using mathematical operations of your choice.


Ugly, but perhaps you want:

if(input < pow(10,N+1)) {
  ostringstream sout;
  sout << input;
  cout << setw(N) << sout.str().substr(0,N);
} else cout << (int)input; 
0

精彩评论

暂无评论...
验证码 换一张
取 消