<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" Inherits="TSPage" title="Multi-currency Nirvana with Java" LocalStylesheet="~/software/articles/stylesheet.css" MetaDescription="An introduction to multi-currency handling issues for retail and financial services products written in Java. By Terry Smith." %>

Multi-currency. The word is not in the dictionary, and 99.9% of the books and tutorials on software globalization overlook the issue altogether. However, for those of you targeting your e-commerce or financial software to the global marketplace, it's not a subject you should overlook. There are many facets to the problems that handling multiple currencies introduces to an application. This article is only an introduction to the seemingly simple problems such as currency arithmetic and localization formatting. Although these can be very difficult in programming languages lacking the appropriate libraries, a few Java classes mixed with a little knowledge of how to use them safely can greatly ease your multi-currency headaches.

Arithmetic

The first hurdle on the path to multi-currency nirvana is being able to safely perform arithmetic on various currency amounts. "Safely" meaning that significant digits are not lost in the rounding process. Java's solution to this problem is the BigDecimal class.

Java's BigDecimal class in the java.math package is used to store arbitrary-precision signed decimal numbers. Let's look at how BigDecimal would store 3.1415. Internally, our number is represented by two integers. The first is a signed 32-bit integer which is our number with the decimal removed, 31415. This is called the unscaled value. The second number is a non-negative 32-bit integer scale, which represents the number of digits to the right of the decimal point. In our example, this value would be 4. So the number represented by BigDecimal is UnscaledValue/(10 * Scale).

BigDecimal is very versatile in its rounding behavior, providing eight different rounding modes. Most financial applications would want to use the round half up mode. This mode rounds up if the discarded fraction is >=.5 and rounds down otherwise. The rounding mode is specified on the divide method as shown in Example 1(a) below:

(a) public BigDecimal divide (BigDecimal divisior,
                              int roundingMode)

(b) public BigDecimal divide (BigDecimal divisor,
                              int scale,
                              int roundingMode)
Example 1: (a)The divide method using this.scale() and (b)scale passed as a parameter.

You can pass in BigDecimal's ROUND_HALF_UP constant for the rounding mode. The above returns a BigDecimal whose value is (this / divisor), and whose scale is this.scale(). If another scale needs to be specified, use the overloaded method shown in Example 1(b).

You should always use BigDecimal for multiplication and division to protect yourself on round-off. Just to be safe, it doesn't hurt to use it for addition and subtraction as well; otherwise, you may find that one-hundred pennies don't add up to a dollar.

Locales

The next multi-currency hurdle is the proper formatting and parsing of amounts. The foundation of these two related topics is the Locale class. Java uses the Locale class in the java.util package in all aspects of localization formatting. It affects language choice, number and currency formats, date and time formats, calendar usage, and other culturally sensitive data representations. This section will examine the Locale class and the associated NumberFormat class for formatting currencies.

A Locale identifies a specific language and a geographic region. In technical jargon these are referred to as the language code and the country code. Note, though, that the country code can be more specific than an individual country. For example, United States (US) and United States Minor Outlying Islands (UM) are both valid country codes.

As the shown by the examples below, language codes are two-letter lower-case identifiers specified by ISO-639. Country codes are two-letter upper-case identifiers specified by ISO-3166.

Sample ISO-639 Language Codes
en English
fr French
ja Japanese
tw Twi
     
Sample ISO-3166 Country Codes
US United States
FR France
FX France, Metropolitan
TG Togo

The following are useful resources for more information:

A Locale instance is nothing more than a wrapper class for the language code and country code. A Locale object can be created from its constructor as follows:

Locale usLocale = new Locale("en", "US");

When the Java Virtual Machine starts up, it queries the underlying OS for a default-locale setting. To obtain this default Locale, use the static getDefault() method:

Locale defaultLocale = Locale.getDefault();

Since a Locale is only a data object, you can construct it with any combination of language and country codes. Validity checking can only be performed by calling the getAvailableLocales() method of a locale-sensitive class in order to determine the Locale definitions it supports (NumberFormat.getAvailableLocales() for example).

You might think that each currency would be formatted according to the standard format for that currency. In fact, no such standard exists. Currencies are typically formatted according to the locale in which they are being used. Let's take Euros for example. The Italian\Italy locale (it_IT) formats Euros as € 1.234.567,89 whereas the Finnish\Finland (fi_FI) locale formats the same amount as 1 234 567,89 €. This is why NumberFormat instances, which we will look at next, are constructed using locales instead of currency codes.

Formatting

NumberFormat in the java.text package defines an interface for formatting and parsing numbers. It allows you to do so independently of any particular locale's conventions for decimal points, thousands-separators, etc. A NumberFormat instance is created through one of several class factory methods. Use getInstance or getNumberInstance to get the normal number format, and use getIntegerInstance, getCurrencyInstance, or getPercentInstance to get various other formatter instances. Each of these comes in two flavors. One that takes a Locale instance as the only argument and another that doesn't have any arguments and simply returns an instance for the default locale.

Example 2 below demonstrates formatting a number using getNumberInstance versus getCurrencyInstance. Note the use of one of the Locale class's many constants for language and country codes.

NumberFormat numberFormatter = NumberFormat.getNumberInstance(
                               Locale.GERMANY );
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(
                                 Locale.GERMANY );

double amount = 1234567.89;
System.out.println( "Formatted as number  : " +
                    numberFormatter.format(amount) );
System.out.println( "Formatted as currency: " +
                    currencyFormatter.format(amount) );

The output is as follows:

Formatted as number  : 1.234.567,89
Formatted as currency: 1.234.567,89 DM
Example 2: Formatting a number using getNumberInstance versus getCurrencyInstance.

I passed a hard-coded double to format() in order to simplify this example. In your application, you want to retrieve a double from a BigDecimal instance in order to ensure that rounding will be handled properly. Otherwise, the format() routine will round according to the IEEE 754 default rounding mode known as half even. This rounding mode helps minimize cumulative error in scientific applications but, as mentioned earlier, you most likely want to use round half up for financial calculations.

Parsing

Speaking of calculations, before you can use BigDecimal for this you will need to get the amounts entered by the user, typically as strings, into BigDecimal instances. Once again you will need a NumberFormat instance. First, obtain a formatter by calling getNumberInstance or getCurrencyInstance on NumberFormat. Then call parse(String) on the formatter passing your amount. It returns a Number interface on which you can then invoke doubleValue(). Next, pass the double value on to the constructor for a BigDecimal instance.

Normally you will want to use getNumberInstance instead of getCurrencyInstance. A U.S. amount such as "123.45" passed to the parse(String) method of a currency instance formatter will throw a ParseException because it doesn't include a leading $. Most applications allow the user to select a currency type using a dropdown menu instead of requiring the currency symbol to be entered by hand (in the exact position no less or a parsing error results), so you will use getNumberInstance most often for parsing amounts.

When obtaining a NumberInstance it is essential to use the correct locale for the currency being parsed or formatted. Problems easily occur with differences in decimal separators like comma versus decimal. Example 3(a) below attempts to convert "123,45" to a double using a formatter for the en_US locale:

(a)
NumberFormat wrongFormat = NumberFormat.getNumberInstance( Locale.US );
Number wrongNumber = wrongFormat.parse( "123,45" );
double wrongAmount = wrongNumber.doubleValue();

(b)
NumberFormat germanFormat = NumberFormat.getNumberInstance( Locale.GERMANY );
Number germanNumber = germanFormat.parse( "123,45" );
double germanAmount = germanNumber.doubleValue();
Example 3:(a) Using an incorrect NumberFormat for parsing a currency versus (b) using one for the correct locale.

The resulting double value in Example 3(a) will be 12345.0, not 123.45. To get the desired behavior you must use a valid formatter. In this case we'll use a German locale for our comma-separated amount as shown in Example 3(b). In this case the value of the double is 123.45 and is now safe to use in any calculations.

Odds and Ends

Remember further above when I said that currencies are typically formatted according to the locale in which they are being used? Well I want to emphasize the word "typically". This is where the fun can really begin in a multi-currency application. A good example is ja_JP locale for Japanese\Japan. The local currency, Japanese Yen, has whole units only, no cents. An amount in U.S. dollars formatted under the ja_JP locale will lose its decimal places and be cents-less (pun intended).

There's no easy answer here. The strategy on how to handle this issue really depends on each application's requirements. The best advice that I can give is to create a class that wraps formatting, parsing, and arithmetic issues and ensure that it's used throughout your application. Then you can easily insert specialized behavior, modify rounding modes, and add other tweaks here and there as needed.

One final note. JDK 1.4 introduced a new Currency class in the java.util package. The designer's of this new class definitely did not go overboard with adding features. The Currency class should be thought of as a data holder object similar to the Locale class. You can pass a Currency instance to several new methods also added in JDK 1.4. DecimalFormatSymbols.setCurrency() is an example. As a utility class, Currency does not offer much. Once you have a Currency object all you can do with it is print its ISO-4217 currency code, get its number of fraction digits, and obtain its symbol for the default locale or for any other locale which you pass to it. Currency instances are created through factory methods that ensure only one instance of the class is ever instantiated per currency. I've found the Currency class rather useless.

Conclusion

Multi-currency support is an often overlooked but challenging problem. With a little knowledge and practice in using them correctly, Java's libraries can free you from many, but not all, of the technical challenges and allow you to focus on the business problem aspects of software globalization.



Did you find this article useful? Vote for it on jfind.com!