0
\$\begingroup\$

My program contains some very large integers and uses the GMP library. The output contains integers (probably small enough to fit into int). I would like to make the output easy to read by adding commas between every three digits with any left overs on the left side.

Examples:

1234 => 1,234
12345 => 12,345
123456 => 123,456
123 => 123

The input of the function would be a string representing an integer, because the value of the mpz_class would be converted to a string. I know there's been a lot of discussion of adding commas to int but not string. The mpz_class can't be converted to an int because it may be too big.

Here is what I have and it isn't pretty.

using namespace std;

/*add commas between groups of 3 digits with remainder on left side*/
string addCommas(string in)
{
    const int length = in.length();
    if(length < 4)
    {
        return in;
    }
    int inserted = 0;
    int i = length % 3;
    if(i == 0)
    {
        i = 3;
    }
    for(; i < length + inserted; i = i + 4)
    {
        in.insert(i, ",");
        inserted++;
    }
    return in;
}

Given its use in the program, it is guaranteed the input will not be a negative number. Though I still wonder if it's good to include a check? The program focuses on quick results. How can this code be more readable?

\$\endgroup\$
  • \$\begingroup\$ There's no definition for your string type. Is it very similar to std::string (if so, why not use std::string?) \$\endgroup\$ – Toby Speight Dec 5 '18 at 11:51
  • \$\begingroup\$ @TobySpeight forgot to include the using namespace std; \$\endgroup\$ – northerner Dec 5 '18 at 12:11
  • \$\begingroup\$ As a side note, I suspect commas are only inserted in numbers of five or more digits. \$\endgroup\$ – bipll Dec 5 '18 at 13:35
  • \$\begingroup\$ The streams already do this automatically. The "C" local has no commas. But most locals have an explicit point where commas go in a number. Thus by setting the correct local in your application you don't need to do anything else. \$\endgroup\$ – Martin York Dec 5 '18 at 23:26
2
\$\begingroup\$

Avoid using namespace std

The standard namespace isn't designed to be imported wholesale, and name collisions could later appear that silently change the meaning of your program (such as when you add an extra include, or move to a newer C++ standard).

std::string::insert moves content multiple times

We're not very efficient, because the later characters will be shuffled as many times as there are preceding commas inserted. Worse, we don't even reserve() enough space to add the commas, so there could (theoretically at least) be multiple memory allocations.

Don't reinvent the wheel

It would be simpler and more flexible to use std::numpunct to perform the formatting.

\$\endgroup\$
1
\$\begingroup\$

You should not do this manually.

The local do this for you correctly for the local you are currently in (with the correct separation character for the locale).

The problem is that by default your code runs in the "C" locale. This uses no seporators. But you can make your code pick up the local used by the current machine.

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

int main()
{
    std::cout.imbue(std::locale(""));

    std::cout << 123456789 << "\n";
}

The output of this is:

123,456,789

In Germany it would look like:

123 456.789  // apparently correct separation for DE

Alternatively if you want your own specific separation you can set up the locale object on the stream to do the work specific to your application.

#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 outputs:

123:456:789
\$\endgroup\$
  • \$\begingroup\$ It would appear imbue(std::locale("")) does not work on my machine. I copy and pasted your example and it had printed 123456789. I also copied the example here and still have no commas. I assume it wasn't working with the string returned from the mpz_class but it seems like something more fundamental is wrong. \$\endgroup\$ – northerner Dec 6 '18 at 9:07
  • \$\begingroup\$ Are there any special options needed for compiling with this? \$\endgroup\$ – northerner Dec 6 '18 at 9:07
  • \$\begingroup\$ @northerner: This means your machine has no current local (or your machines locale is "C"). You should probably look at the machine configuration and make sure it is correctly configured. But You can force a locale by setting the environment variable LC_ALL. Example: > LC_ALL=en_US ./myProgram \$\endgroup\$ – Martin York Dec 6 '18 at 16:52
  • \$\begingroup\$ There is nothing special you need to do. The locale code will ask the OS what the current locale is. So if the OS does not know then you will not get any change. But usually most machines are configured with a current locale (or at least a location and default language setting). \$\endgroup\$ – Martin York Dec 6 '18 at 16:57
  • \$\begingroup\$ Where would I put these commands in? I'm running Windows with MinGW. \$\endgroup\$ – northerner Dec 7 '18 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.