Writing and Reading Numbers
Problem
You need to write a number to a stream in a formatted way that obeys local conventions, which are different depending on where you are.
Solution
Imbue the stream you are writing to with the current locale and then write the numbers to it, as in Example 13-2, or you can set the global locale and then create a stream. The latter approach is explained in the discussion.
Example 13-2. Writing numbers using localized formatting
#include #include #include using namespace std; // There is a global locale in the background that is set up by the // runtime environment. It is the "C" locale by default. You can // replace it with locale::global(const locale&). int main( ) { locale loc(""); // Create a copy of the user's locale cout << "Locale name = " << loc.name( ) << endl; cout.imbue(loc); // Tell cout to use the formatting of // the user's locale cout << "pi in locale " << cout.getloc( ).name( ) << " is " << 3.14 << endl; }
Discussion
Example 13-2 shows how to use the user's locale to format a floating-point number. Doing so requires two steps, creating an instance of the locale class and then associating, or imbuing, the stream with it.
To begin with, Example 13-2 creates loc, which is a copy of the user's locale. You have to do this using locale's constructor with an empty string (and not the default constructor), like this:
locale loc("");
The difference is subtle but important, and I'll come back to it in a moment. Creating a locale object in this way creates a copy of the "user's locale," which is something that is implementation defined. This means that if the machine has been configured to use American English, locale::name( ) will return a locale string such as "en_US", "English_United States.1252", "english-american", and so on. The actual string is implementation defined, and the only one required to work by the C++ standard is "C".
By comparison, locale's default constructor returns a copy of the current global locale. There is a single, global locale object for every C++ program that is run (probably implemented as a static variable somewhere in the runtime libraryexactly how this is done is implementation defined). By default, it is the C locale, and you can replace it with locale::global(locale& loc). When streams are created, they use the global locale at the time of creation, which means that cin, cout, cerr, wcin, wcout, and wcerr use the C locale, so you have to change them explicitly if you want the formatting to obey a certain locale's conventions.
Locale names are not standardized. Usually, however, they look something like this:
_.
Where language is either a full language name, such as "Spanish", or a two-letter code, such as "sp"; country is a country, such as "Colombia", or a two-letter country code such as "CO", and code page is the code page, e.g., 1252. The language is the only required part. Experiment using explicit locales on various systems to get a feel for what the different names will look like using different compilers. If the locale name you use is invalid, it will throw a runtime_error. Example 13-3 gives a few examples of explicit locale names.
Example 13-3. Naming locales explicitly
#include #include #include #include using namespace std; int main( ) { try { locale loc(""); cout << "Locale name = " << loc.name( ) << endl; locale locFr("french"); locale locEn("english-american"); locale locBr("portuguese-brazilian"); cout.imbue(locFr); // Tell cout to use French formatting cout << "3.14 (French) = " << 3.14 << endl; cout << "Name = " << locFr.name( ) << endl; cout.imbue(locEn); // Now change to English (American) cout << "3.14 (English) = " << 3.14 << endl; cout << "Name = " << locEn.name( ) << endl; cout.imbue(locFr); // Tell cout to use Brazilian formatting cout << "3.14 (Brazil) = " << 3.14 << endl; cout << "Name = " << locBr.name( ) << endl; } catch (runtime_error& e) { // If you use an invalid locale name, a runtime_error exception // is thrown. cerr << "Error: " << e.what( ) << endl; } }
The output of this program on Windows with Visual C++ 7.1 looks like this:
Locale name = English_United States.1252 3.14 (French) = 3,14 Name = French_France.1252 3.14 (English) = 3.14 Name = English_United States.1252 3.14 (Brazil) = 3,14 Name = Portuguese_Brazil.1252
You can see that my machine's locale is U.S. English using codepage 1252. The example also shows pi using a couple of other locales. Note that France and Brazil use a comma instead of a decimal point. The thousands separator is different, too: French and Portuguese use a space instead of a comma, so that 1,000,000.25 in America would be written as 1 000 000,25 in French and Portuguese.
Creating locales with explicit names is something you shouldn't have to do in most cases anyway. For using locales to print numbers, dates, currency, or anything else, you should simply instantiate a locale using an empty string, and imbue your streams with it.
Locale behavior can be a bit confusing, so I will summarize important points:
- The default global locale is the "C" locale, because it is the only one guaranteed to exist in every implementation, per the standard.
- The standard streams are all created using the global locale at program start-up, which is the "C" locale.
- You can create a copy of the user's current runtime locale by passing an empty string to the locale constructor, e.g., locale("").
- You can create a locale object for a named locale by passing in a string that identifies the locale, e.g., locale("portuguese-brazilian"). The strings are not standardized, though.
- Once you have a locale object that represents the user's default locale or a named locale, you can set the global locale with locale::global. All streams that are created subsequently will use the global locale.
- You can set the locale for a stream explicitly with the imbue member function.
When writing software to use locales, only use localized formatting for user-visible data. That is, if you need to display a number in a format the user is familiar with, instantiate a locale and imbue the stream with it to display the number correctly to the user. But if you are writing data to a file or some other intermediate serialized storage, use the C locale for portability. If your code explicitly changes the global locale, then you will need to explicitly imbue your file streams with the C locale. You can do this two ways, by creating a locale using the name "C," or by calling locale::classic( ), like this:
ofstream out("data.dat"); out.imbue(locale::classic( )); out << pi << endl; // Write using C locale
Reading numbers is similar. For example, to read in a number in French and write it in the C locale, do this:
double d; cin.imbue(locale("french")); cin >> d; cout << "In English: " << d;
If you run this program and enter 300,00, it will print out 300.
To make a stream obey a locale's numeric conventions, explicitly imbue the stream with the target locale object. Or, if you want all streams created to use a particular locale, install it as the global locale. Currency is handled somewhat differently; see Recipe 13.4 for examples of how to write and read currency.
See Also
Recipe 13.4