tags:

views:

227

answers:

3

I've got a simple class Currency with overloaded operator<<. I don't know how can i separate the number with spaces every 3 digits, so it looks like: "1 234 567 ISK".

#include <cstdlib>
#include <iostream>

using namespace std;

class Currency
{
    int val;
    char curr[4];

    public:
    Currency(int _val, const char * _curr)
    {
        val = _val;
        strcpy(curr, _curr);
    }

    friend ostream & operator<< (ostream & out, const Currency & c);
};

ostream & operator<< (ostream & out, const Currency & c)
{
    out << c.val<< " " << c.curr;
    return out;
}

int main(int argc, char *argv[])
{
    Currency c(2354123, "ISK");
    cout << c;
}

What interests me, is somehow the easiest solution for this particular situation.

+2  A: 
struct Currency {
  static char const sep = ' ';
  static int const group_size = 3;

  Currency(int val, std::string unit)
  : val(val), unit(unit)
  {}

  friend std::ostream& operator<<(std::ostream& out, Currency const& v) {
    // currently ignores stream width and fill
    std::ostringstream ss;
    bool const neg = v.val < 0;
    int const val = (neg ? -v.val : v.val);
    if (neg) out << '-';
    ss << val;
    std::string const s = ss.str();
    std::string::size_type n = s.size() % v.group_size;
    if (n) out << s.substr(0, n);
    for (; n < s.size(); n += v.group_size) {
      out << sep << s.substr(n, v.group_size);
    }
    out << ' ' << v.unit;
    return out;
  }

private:
  int val;
  std::string unit;
};

Could make sep and group_size non-static members, if you want to customize each object with comma, etc. (If so, make them private and initialize in the ctor, probably with default parameter values.) You could also use a traits class controlling output formatting.

Locales also support currency formatting through the moneypunct facet.

Roger Pate
+5  A: 

This can be done with facets

struct myseps : numpunct<char> { 
   /* use space as separator */
   char do_thousands_sep() const { return ' '; } 

   /* digits are grouped by 3 digits each */
   string do_grouping() const { return "\3"; }
};

int main() {
  std::cout.imbue(std::locale(std::locale(), new myseps));
  std::cout << 10000; // 10 000
}

Alternatively, you may code your own loop

void printGrouped(ostream &out, int n) {
  if(n < 0) {
    out << "-";
    return printGrouped(out, -n);
  }

  if(n < 1000) {
    out << n;
  } else {
    printGrouped(out, n / 1000);
    out << " " << setw(3) << setfill('0') << (n % 1000);
  }
}

ostream & operator<< (ostream & out, const Currency & c) {
    printGrouped(out, c.val);
    out << " " << c.curr;
    return out;
}
Johannes Schaub - litb
Though changing how *all* numbers are printed is almost certainly not desired.
Roger Pate
@Roger fair point. Added a custom loop.
Johannes Schaub - litb
`locale("")` is implementation-defined. on OS X 10.5 it won't work (throws a `runtime_exception` with `what()` "locale::facet::_S_create_c_locale name not valid"). You can use the current global locale with the default constructor instead.
wilhelmtell
@wilhelmtell i used `locale("")` on purpose because according to stroustrup's locale appendix in TC++PL, it uses the "user's preferred locale" and the C++ Standard says that valid arguments constitute "C" and "" (i suspect "" is supposed to use some environment variables?). Changing to default constructor for now. Someone has a clue what happens on OSX that it doesn't work?
Johannes Schaub - litb
Thank You for clear and nice tip with division / modulo 1000. That's what i was looking for.
dygi
+4  A: 

One possibility might be to use locales for this.

#include <locale>
#include <string>
#include <cstddef>

class SpaceSeparator: public std::numpunct<char>
{
public:
    SpaceSeparator(std::size_t refs): std::numpunct<char>(refs) {}
protected:
    char do_thousands_sep() const { return ' '; }
    std::string do_grouping() const { return "\03"; }
};

//...    
ostream & operator<< (ostream & out, const Currency & c)
{
    SpaceSeparator facet(1); //1 - don't delete when done
    std::locale prev = out.imbue(std::locale(std::locale(), &facet));
    out << c.val<< " " << c.curr;
    out.imbue(prev);  //restore previous locale
    return out;
}
UncleBens
+1 for a cleaner locale solution :)
Johannes Schaub - litb
However, std::moneypunct would be a better facet to use.
Roger Pate