How to insert spaces in a big number to make it more readable?
Asked Answered
E

6

6

I came up with this, since other examples provided on stackoverflow were in C#

string number_fmt(ulong n)
{
    // cout << "(" << n << ")" << endl;
    char s[128];
    sprintf(s, "%lu", n);
    string r(s);
    reverse(r.begin(), r.end());
    int space_inserted = 0;
    size_t how_many_spaces = r.length() / 3;

    if(r.length() % 3 != 0)
        how_many_spaces += 1;

    for(int i = 1; i < how_many_spaces; ++i)
    {
        r.insert(3 * i + space_inserted, " ");
        space_inserted += 1;
    }
    reverse(r.begin(), r.end());

    return r;
}

Do you know any better solution ?

Elspeth answered 31/8, 2011 at 13:53 Comment(4)
What's not good about this one? That would help pick something better.Saprogenic
Does it have to be spaces? You can try using the <locale> header functionality: cplusplus.com/forum/beginner/24707Lake
possible duplicate of How do you set the cout locale to insert commas as thousands separators?Korry
Yes. The streams already have a local object that will format numbers for you. Doing it manually is not symmetric (by using the local you get symmetric input and output automatically (with no work)). The problem is that applications by default are in the "C" local. Just set the machine default local ("") and it will start formatting numbers correctly for your locale.Purcell
J
2

This one is different, but better is subjective. I think it's very succinct and clear what it's doing though:

string number_fmt(unsigned long long n, char sep = ',') {
    stringstream fmt;
    fmt << n;
    string s = fmt.str();
    s.reserve(s.length() + s.length() / 3);

    // loop until the end of the string and use j to keep track of every
    // third loop starting taking into account the leading x digits (this probably
    // can be rewritten in terms of just i, but it seems more clear when you use
    // a seperate variable)
    for (int i = 0, j = 3 - s.length() % 3; i < s.length(); ++i, ++j)
        if (i != 0 && j % 3 == 0)
            s.insert(i++, 1, sep);

    return s;
}

Using it like

cout << number_fmt(43615091387465) << endl;

prints

43,615,091,387,465
Junji answered 31/8, 2011 at 14:33 Comment(1)
This is a bad idea. Not only does the local object on a stream already do it it is a lot more flexible in how it does it. Also be using the local object you input/output that is symmetric (ie ir will print with commas and read back with commas. Also by using the local you get the number printed that is local specific and not restricted to a specific style (in this case European-anglo).Purcell
P
6

I don't know about "better", but this version uses std::locale, etc.

#include <iostream>
#include <locale>
#include <sstream>

template<class Char>
class MyFacet : public std::numpunct<Char> {
public:
  std::string do_grouping() const { return "\3"; }
  Char do_thousands_sep() const { return ' '; }
};

std::string number_fmt(unsigned long n)
{
  std::ostringstream oss;
  oss.imbue(std::locale(oss.getloc(), new MyFacet<char>));
  oss << n;
  return oss.str();
}

int main() {
  std::cout << number_fmt(123456789) << "\n";
}


EDIT: Of course, if your final goal is to print the values on an ostream, you can skip storing them in a string altogether.
#include <iostream>
#include <locale>
#include <sstream>
#include <cwchar>

template <class Char>
class MyFacet : public std::numpunct<Char> {
public:
  std::string do_grouping() const { return "\3"; }
  Char do_thousands_sep() const { return ' '; }
};

int main(int ac, char **av) {
  using std::locale;
  using std::cout;

  // Show how it works to start with
  cout << 123456789 << "\n";

  // Switch it to spacey mode
  locale oldLoc =
    cout.imbue(locale(cout.getloc(), new MyFacet<char>));

  // How does it work now?
  cout << 456789123 << "\n";

  // You probably want to clean up after yourself
  cout.imbue(oldLoc);

  // Does it still work?
  cout << 789123456 << "\n";
}
Pralltriller answered 31/8, 2011 at 14:57 Comment(2)
Yes use the local but why then use a string stream?Purcell
@Tux - because the scope of the original question to convert a number to a string, not to send a number to stdout. I'll update my answer to highlight the simpler alternative.Jaynajayne
P
3

This is already done by the locale.

The default local is "C" which does no formatting. But you can set it to your current language-specific local (as defined by your computer's setting by setting the current local as the first line of main).

int main()
{
    std::locale::global(std::locale("")); // Set the default local of the machine
                                          // Will be used by all streams.
                                          // The "" will find the machine specific local
                                          // and use that instead of the "C" locale
                                          // Note: The C local should only be used for programmers.

    // Alternatively you can imbue particular stream with the local
    // To achieve a localized effect
    // std::cout.imbue(std::locale(""));

    // Now all you do is print the number.
    std::cout << "123456789\n";  // This will print the number according to your local
}                                // For me US-en this is   123,456,789
                                 // Your may very.

If you want to do something explicitly then you can set a facet in the local for printing numbers.

#include <iostream>
#include <locale>
#include <string>


template<typename CharT>
struct Sep : public std::numpunct<CharT>
{
        virtual std::string do_grouping()      const   {return "\003";}
        virtual CharT       do_thousands_sep() const   {return ':';}
};

int main()
{
        std::cout.imbue(std::locale(std::cout.getloc(), new Sep <char>()));

        std::cout << 123456789 << "\n";   // this prints 123:456:789
}
Purcell answered 31/8, 2011 at 16:9 Comment(0)
J
2

This one is different, but better is subjective. I think it's very succinct and clear what it's doing though:

string number_fmt(unsigned long long n, char sep = ',') {
    stringstream fmt;
    fmt << n;
    string s = fmt.str();
    s.reserve(s.length() + s.length() / 3);

    // loop until the end of the string and use j to keep track of every
    // third loop starting taking into account the leading x digits (this probably
    // can be rewritten in terms of just i, but it seems more clear when you use
    // a seperate variable)
    for (int i = 0, j = 3 - s.length() % 3; i < s.length(); ++i, ++j)
        if (i != 0 && j % 3 == 0)
            s.insert(i++, 1, sep);

    return s;
}

Using it like

cout << number_fmt(43615091387465) << endl;

prints

43,615,091,387,465
Junji answered 31/8, 2011 at 14:33 Comment(1)
This is a bad idea. Not only does the local object on a stream already do it it is a lot more flexible in how it does it. Also be using the local object you input/output that is symmetric (ie ir will print with commas and read back with commas. Also by using the local you get the number printed that is local specific and not restricted to a specific style (in this case European-anglo).Purcell
C
1

Admittedly, if one wanted to have the most possible efficient version and didn't mind specializing it for the case at hand, using a local char buffer can help a lot.

#include <iostream>
#include <string>

std::string format(unsigned long long i) {
  char buffer[128]; // can be adapted more tightly with std::numeric_limits

  char* p = buffer + 128;
  *(--p) = '\0';

  unsigned char count = 0;
  while (i != 0) {
    *(--p) = '0' + (i % 10);
    i /= 10;

    if (++count == 3) { count = 0; *(--p) = ' '; }
  }

  return p;
}

int main() {
  std::cout << format(1234567890) << '\n';
}

In action at ideone:

1 234 567 890

(Key point: for number printing, go backward)

Creese answered 31/8, 2011 at 15:22 Comment(0)
Z
1

Not very optimal but small

QString str = QString::number(d);
for (int i = 3; str.size() > i; i += 4)
   str.insert(str.size() - i, ' ');
Zoril answered 28/1, 2020 at 7:56 Comment(0)
C
0

If "better" means more efficient, you should:

  1. use reserve on the output string (you know its size...)

  2. avoid the insert in the middle of the string, because you have to copy a big part of the string each time you do that.

I would say something like this (untested):

std::string number_fmt (ulong n)
{
  std::ostringstream buff;
  buff << n;
  std::string without_spaces = buff.str ();
  std::string with_spaces;
  with_spaces.reserve ((without_spaces.size () * 4) / 3);
  std::size_t nb_inserted = 0;

  for (auto it = without_spaces.rbegin (); it != without_spaces.rend (); ++it)
  {
     if (nb_inserted % 3  ==  0  &&  nb_inserted != 0)
     {
         with_spaces.push_back (' ');
     }
     ++ nb_inserted;

     with_spaces.push_back (*it);   
  }
  std::reverse (with_spaces.begin (), with_spaces.end ());
  return with_spaces;
}
Crimpy answered 31/8, 2011 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.