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

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net