Inserting commas to numbers
OnlineCop

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.

commas_to_numbers.cpp#SelectExpand
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
Matthew Leverton

Untested, and probably some off-by-one errors:

#SelectExpand
1char *number_format(int n) 2{ 3 int i = 0, len = 4 * log10(n) / 3 + 2; 4 char *c = malloc(len); 5 char *p = c + len - 1; 6 *p-- = 0; 7 8 do 9 { 10 if (i++ == 3) 11 { 12 *p-- = ','; 13 i = 0; 14 } 15 *p-- = '0' + n % 10; 16 n /= 10; 17 } while (n); 18 19 return p; 20}

Oh, and it's for positive numbers only.

OnlineCop

#SelectExpand
1char *number_format(int n)

Oh, and it's for positive numbers only.

Wouldn't it make sense, then, to change it to:

#SelectExpand
1char *number_format(unsigned int n)

and save yourself the headache of the possibility of the negativity?

Matthew Leverton

Here, now it should work fine:

#SelectExpand
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

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

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.

#SelectExpand
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.

#SelectExpand
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

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.

#SelectExpand
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

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:

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

#SelectExpand
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
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

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

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

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.
If you don't need to specify the culture for the particular statement, then the following is also equivalent:

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

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

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.

Thread #601301. Printed from Allegro.cc