I came up with this, since other examples provided on stackoverflow w开发者_如何学Goere 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 ?
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";
}
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
}
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
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)
Not very optimal but small
QString str = QString::number(d);
for (int i = 3; str.size() > i; i += 4)
str.insert(str.size() - i, ' ');
If "better" means more efficient, you should:
use
reserve
on the output string (you know its size...)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;
}
精彩评论