|
|
| Inserting commas to numbers |
|
OnlineCop
Member #7,919
October 2006
|
So a quick Google search didn't turn up anything useful in adding commas to numbers (like "1000") in standard C or C++ (preferably C++), so I wrote my own. Can I get some feedback on the attached source as to how I can/should do it better? I've also wrapped it in <code></code> tags below since it's so short. Using std::stringstream is probably using a hammer to drive a thumbtack, but I've been having problems wrapping my mind around simpler solutions. 1#include <string>
2#include <sstream>
3#include <iostream>
4
5
6/* Step 1: 'value' needs to be converted (back) to a string.
7 * Step 2: Determine how many commas need to be added based on the length of
8 * the generated string:
9 * "0".."999" contains 1..3 characters: add 0 commas
10 * "1000".."999999" contains 4..6 characters: add 1 comma
11 * "1000000".."999999999" contains 7..9 characters: add 2 commas
12 * So ((num_chars-1) / 3) gives the number of commas to add.
13 * Step 3: Push commas into the final string array.
14 */
15std::string add_commas(const int& value)
16{
17 std::stringstream ss;
18 ss.str(""); // Clear the buffer
19
20 /* Negative sign will throw off calculation due to the extra '-' at the
21 * front. Remove it until the very end.
22 */
23 if (value < 0)
24 ss << -value;
25 else
26 ss << value;
27
28 std::string result(ss.str());
29
30 unsigned num_chars = result.length();
31 unsigned num_commas = (num_chars - 1) / 3;
32
33 for (unsigned i = 0; i < num_commas; ++i)
34 {
35 result.insert((num_chars - 3) - 3 * i, ",");
36 }
37
38 // Need to re-insert leading '-' sign?
39 if (value < 0)
40 result.insert(0, "-");
41
42 return result;
43}
44
45
46void show_usage(char* filename)
47{
48 std::cout << filename << ": Add commas to numbers >= 1000.\n"
49 << " Example: Entering ``1000'' results in ``1,000'' and\n"
50 << " ``1234567'' results in ``1,234,567'', while\n"
51 << " ``0'' through ``999'' remain ``0'' through\n"
52 << " ``999'', respectively.\n"
53 << std::endl
54 << " Note: There is no error-checking, so if you pass in a\n"
55 << " non-number value, you'll crash the program. You\n"
56 << " have been warned.\n"
57 << std::endl;
58}
59
60
61int main(int argc, char* argv[])
62{
63 if (argc == 1)
64 {
65 show_usage(argv[0]);
66 return 0;
67 }
68
69 for (int i = 1; i < argc; ++i)
70 {
71 std::cout << "\t#" << i << ": " << add_commas(atoi(argv[i])) << std::endl;
72 }
73
74 return 0;
75}
|
|
LennyLen
Member #5,313
December 2004
|
|
Matthew Leverton
Supreme Loser
January 1999
|
Untested, and probably some off-by-one errors: Oh, and it's for positive numbers only. |
|
OnlineCop
Member #7,919
October 2006
|
Matthew Leverton said:
1char *number_format(int n)
Oh, and it's for positive numbers only. Wouldn't it make sense, then, to change it to: 1char *number_format(unsigned int n)
and save yourself the headache of the possibility of the negativity?
|
|
Matthew Leverton
Supreme Loser
January 1999
|
Here, now it should work fine: 1char *number_format(int n)
2{
3 int i = -1, len, negative = 0;
4 char *c, *p;
5
6 if (n == 0)
7 len = 2;
8 else if (n < 0)
9 {
10 negative = 1;
11 n = -n;
12 len = 4 * floor(log10(n)) / 3 + 3;
13 }
14 else
15 len = 4 * floor(log10(n)) / 3 + 2;
16
17 c = malloc(len);
18 p = c + len - 1;
19 *p-- = 0;
20
21 do
22 {
23 if (++i == 3)
24 {
25 *p-- = ',';
26 i = 0;
27 }
28 *p-- = '0' + n % 10;
29 n /= 10;
30 } while (n);
31
32 if (negative) *p = '-';
33
34 return c;
35}
|
|
Tobias Dammers
Member #2,604
August 2002
|
That looks incredibly complicated for something this simple... can't you just do something like: string format_with_commas(int number) { stringstream strs; strs << dec << number; string str = strs.str().reverse(); string result; for (size_t i = 0; i < str.length; ++i) { if (i && (i % 3 == 0)) { result = "," + result; } result += str[i]; } return result; }
--- |
|
anonymous
Member #8025
November 2006
|
I think locales are meant for that, although it seems very unportable. This compiles and prints the number with commas on Windows XP when compiled with VC 2005, but throws an exception with GCC. 1#include <iostream>
2#include <locale>
3#include <exception>
4#include <sstream>
5
6int main()
7{
8 try {
9 const char* name = "english-us";
10 std::cout.imbue(std::locale(name));
11 std::cout << 18729873 << '\n';
12
13 std::stringstream ss;
14 ss.imbue(std::locale(name));
15 ss << 18729873;
16 std::cout << ss.str() << '\n';
17
18 } catch (std::exception& e) {
19 std::cout << e.what() << '\n';
20 }
21}
However, with my native locale, it should use spaces as thousands separators, but it prints a with an accent mark instead. Locales are an unknown territory to me but following some online tutorial and dinkumware's reference, I came up with this that can also format numbers in my locale. 1#include <iostream>
2#include <locale>
3#include <exception>
4#include <sstream>
5
6template <class charT>
7class num_printer
8 : public std::numpunct<charT>
9{
10public:
11 num_printer(charT thousands, charT decimals): thousands(thousands), decimals(decimals) {}
12protected:
13 charT do_decimal_point() const { return decimals; }
14 charT do_thousands_sep() const { return thousands; }
15 std::string do_grouping() const { return "\3"; }
16private:
17 charT thousands, decimals ;
18};
19
20int main()
21{
22 try {
23 std::locale local(std::locale(""), new num_printer<char>(' ', ','));
24 std::cout.imbue(local);
25 std::cout << 9873.56 << '\n';
26 std::stringstream ss;
27 ss.imbue(local);
28 ss << 18729873;
29 std::cout << ss.str() << '\n';
30
31 } catch (std::exception& e) {
32 std::cout << e.what() << '\n';
33 }
34}
|
|
ImLeftFooted
Member #3,935
October 2003
|
Uh well without going in depth to the actual ideal solution, your use of stringstream is frustraitinly awful. Let me clean it up a bit. 1std::string add_commas(const int& value)
2{
3 std::stringstream ss;
4
5 ss << value;
6
7 while(ss.good()) {
8
9 ss.seekp(ss.ignore().tellg());
10
11 if((value < 0 ? ss.tellg() > 2 : 0) == (ss.tellg() % 3))
12 ss.put(',');
13 }
14
15 return ss.str();
16}
There. That is a good (but untested) way to do it with stringstream. Also, there is a much quicker way to do something I saw in your code. std::string result(ss.str()); unsigned num_chars = result.length(); You don't need a string to get the stream length! Use tellp which stands for "tell me the put pointer." The 'put pointer' is the character after the last one (aka. the size!) unsigned num_chars = ss.tellp(); The second line below is pointless. A buffer starts out cleared! std::stringstream ss; ss.str(""); // Clear the buffer
|
|
OnlineCop
Member #7,919
October 2006
|
sigh I guess I forgot that I've got to wrap my mind around an international market when I program anymore. I've always just grown up thinking that "192,168,100,100" was Bill Gates' annual salary and "192.168.100.100" was a local IP address. I keep forgetting that commas are different if I change locales. Some of you are reusing the std::stringstream route... is there a reason that you're going that direction, like I had done? The first two posts by LennyLen and Matthew were mostly bare-bones, vanilla C implementations. EDIT: Dustin Dettmer said: unsigned num_chars = ss.tellp(); I actually looked up tellp(), and tried it in a test case before posting my results here. I didn't use it here because when I used it in my test code, it returned a length of 0 in each case. Either I did something wrong, or I had input the data wrong somehow (it was right after the 26 ss << value;
line, and said that "value" gave a length of 0, while the string length gave an actual length. It was a kludge, but it worked). Quote: The second line below is pointless. A buffer starts out cleared! std::stringstream ss; ss.str(""); // Clear the buffer
Yeah, I know. That's actually because I use std::stringstream's a lot, and I am used to clearing the stream in this manner; it was left in more from habit than real necessity. Thanks for the spot, Dustin.
|
|
ImLeftFooted
Member #3,935
October 2003
|
OnlineCop said: The first two posts by LennyLen and Matthew were mostly bare-bones, vanilla C implementations. Because C fails at string manipulation (especially this one) while C++ owns at this particular string problem. |
|
Tobias Dammers
Member #2,604
August 2002
|
Dustin Dettmer said: Because C fails at string manipulation (especially this one) while C++ owns at this particular string problem. How about some C# then? string FormatWithCommas(double value) { return value.ToString("#,#", CultureInfo.InvariantCulture)); }
--- |
|
OnlineCop
Member #7,919
October 2006
|
Tobias, explain to me what's going on there. I give you the number "1000" and it returns "1,000"? And "100000" returns "100,000" or "1000000000" correctly yields "1,000,000,000", and so forth? No hidden strings attached?
|
|
Tobias Dammers
Member #2,604
August 2002
|
It's part of .NET's string formatting routines. Here's the documentation. It's a bit counter-intuitive, but the format string "#,#" means format the number normally, and insert the applicable culture's thousands-separator where appropriate. The applicable culture (the .NET equivalent of a locale) is specified explicitly here as the "Invariant" culture, which more or less means the US-English culture; hence, the thousands-separator is the comma. If you specify a different culture instead, or leave it to .NET to decide for you (which means the local system's locale is generally selected unless the thread's culture has been changed somewhere else), the result may differ. return String.Format("{0:#,#}", value); You can even use these in C++, but you'll have to use MSVC++ and managed C++, which is really an abomination. --- |
|
Mika Halttunen
Member #760
November 2000
|
Here's the same for Java (1.5+): return String.format("%,d", integerValue); // Uses the system (default) locale // or return String.format(Locale.US, "%,d", integerValue); // Uses the specified locale, US here (String.format is a convenience method for Formatter class.) |
|
bamccaig
Member #7,536
July 2006
|
All C and C++ needs are people with experience in more modern frameworks to bring them up to speed. There's no reason that C# or Java code couldn't work in C++ too. It just hasn't been implemented by anyone, or at least not in a freely available and popular library... I'd love to see a rich standard library for both. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
|