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.
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.
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.
| en |
English |
| fr |
French |
| ja |
Japanese |
| tw |
Twi |
|
|
| 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.
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.
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.
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.
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!