Writing and Reading Currency
Problem
You need to write or read a formatted currency value to or from a stream.
Solution
Use the money_put and money_get facets to write and read currency, as shown in Example 13-6.
Example 13-6. Writing and reading currency
#include #include #include #include using namespace std; long double readMoney(istream& in, bool intl = false) { long double val; // Create a reader facet const money_get& moneyReader = use_facet >(in.getloc( )); // End marker istreambuf_iterator end; // State variable for detecting errors ios_base::iostate state = 0; moneyReader.get(in, end, intl, in, state, val); // failbit will be set if something went wrong if (state != 0 && !(state & ios_base::eofbit)) throw "Couldn't read money! "; return(val); } void writeMoney(ostream& out, long double val, bool intl = false) { // Create a writer facet const money_put& moneyWriter = use_facet >(out.getloc( )); // Write to the stream. Call failed( ) (the return value is an // ostreambuf_iterator) to see if anything went wrong. if (moneyWriter.put(out, intl, out, out.fill( ), val).failed( )) throw "Couldn't write money! "; } int main( ) { long double val = 0; float exchangeRate = 0.775434f; // Dollars to Euros locale locEn("english"); locale locFr("french"); cout << "Dollars: "; cin.imbue(locEn); val = readMoney(cin, false); cout.imbue(locFr); // Set the showbase flag so the currency char is printed cout.setf(ios_base::showbase); cout << "Euros: "; writeMoney(cout, val * exchangeRate, true); }
If you run Example 13-6, your output might look like this:
Dollars: $100 Euros: EUR77,54
Discussion
The money_put and money_get facets write and read formatted currency values to and from a stream. They work almost identically to the date/time and numeric facets described in previous recipes. The standard requires instantiations of these for narrow and wide characters, e.g., money_put and money_put. As with the other facets, the get and put functions are verbose, but once you use them a few times, the parameters are easy to remember. money_get and money_put use a moneypunct class that stores formatting information.
First, let's discuss writing money to a stream. The display of currency involves several pieces: the currency sign, the positive or negative sign, the thousands separator, and the decimal point. Most of these are optional, except the decimal point.
You create a money_put object with a character type and a locale, like this:
const money_put& moneyWriter = use_facet >(out.getloc( ));
Both the char and wchar_t versions of money_put are required by the C++ standard. It is a good idea to use the locale of the stream you are writing to to avoid mismatches that result from trying to keep the stream and the money_put object in sync. Next, call the put method to write the currency value to an output stream:
if (moneyWriter.put(out, // Output iterator intl, // bool: use intl format? out, // ostream& out.fill( ), // fill char to use val) // currency value as long double .failed( )) throw "Couldn't write money! ";
money_put::put writes the date to the output stream you pass it using the locale it (the money_put object) was created with. money_put::put returns an ostreambuf_iterator that points to one past the last character output, which has a member function failed you can call to see if the iterator is in a corrupt state.
The parameters to money_put::put are all self-explanatory, except maybe the second one (the intl argument in the example). It is a bool that determines whether the currency symbol is used (e.g., $,
Writing currency to an output stream obeys some of the formatting flags on the stream. Here is each flag and the effect it has on currency:
ios_base::internal
Wherever there is a space or nothing in the formatting of the currency, the fill character will be used (and not a space). See the discussion of moneypunct below for more information about the patterns used for formatting.
ios_base::left and ios_base::right
Causes the currency value to be left or right justified, and the remaining space up to the width value is padded with the fill character (see the description of width next). This is handy because it makes for easy tabular formatting of currency.
ios_base::width
money_put values will follow the standard rules for stream field width. By default, values are left justified. If the field is larger than the value, the fill character given to money_put is used.
ios_base::showbase
When this is true, the currency symbol is printed; otherwise, it is not.
As I said earlier, money_get and money_put use a moneypunct class, which is what actually stores the formatting information. You don't need to worry about the moneypunct class unless you are implementing a standard library, but you can use it to explore the formatting used for a particular locale. moneypunct contains information such as the currency symbol used, the character used for the decimal point, the format of positive and negative values, and so on. Example 13-7 presents a short program for printing out currency format information for a given locale.
Example 13-7. Printing currency format info
#include #include #include using namespace std; string printPattern(moneypunct::pattern& pat) { string s(pat.field); // pat.field is a char[4] string r; for (int i = 0; i < 4; ++i) { switch (s[i]) { case moneypunct::sign: r += "sign "; break; case moneypunct::none: r += "none "; break; case moneypunct::space: r += "space "; break; case moneypunct::value: r += "value "; break; case moneypunct::symbol: r += "symbol "; break; } } return(r); } int main( ) { locale loc("danish"); const moneypunct& punct = use_facet >(loc); cout << "Decimal point: " << punct.decimal_point( ) << ' ' << "Thousands separator: " << punct.thousands_sep( ) << ' ' << "Currency symbol: " << punct.curr_symbol( ) << ' ' << "Positive sign: " << punct.positive_sign( ) << ' ' << "Negative sign: " << punct.negative_sign( ) << ' ' << "Fractional digits: " << punct.frac_digits( ) << ' ' << "Positive format: " << printPattern(punct.pos_format( )) << ' ' << "Negative format: " << printPattern(punct.neg_format( )) << ' '; // Grouping is represented by a string of chars, but the meaning // of each char is its integer value, not the char it represents. string s = punct.grouping( ); for (string::iterator p = s.begin( ); p != s.end( ); ++p) cout << "Groups of: " << (int)*p << ' '; }
Most of these methods are self-explanatory, but a few require further explanation. First, the grouping method returns a string of characters that is interpreted as a string of integers. Each character represents the grouping at that particular index in the number, starting at the right side of the number. And if there is no value for an index, the value for the next previous index is used. In other words, for standard American formatting, there will be a value of 3 at index 0 in the string, which means at index 0, the numbers should be grouped in triplets. Since there are no more values, all indexes greater than zero should also use grouping in triplets.
pos_format and neg_format return an object of type moneypunct::pattern, which has a member field that is a T[4], where T is the character type. Each element in field is one of the enumerations moneypunct::part, which has five possible values: none, space, symbol, sign, and value. A string representation of currency has four parts (thus the array of length four). Typically, the sequence of parts will be something like symbol space sign value, which would mean to print a value as $ -32.00. Often, the positive sign is the empty string since a value with no sign is generally assumed to be positive. The negative sign can be more than one character, such as "( )," in which case the first character is printed where the symbol part occurs in neg_format, and the remainder is printed at the end, so you can have negative values represented as $(32.00).
Most of the time you will not need to worry about the formatting information stored in moneypunct. But if you have to do a lot of formatting of money in different locales, it's worthwhile to experiment and see how different locales are formatted.
See Also
Recipe 13.2 and Recipe 13.3