IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Magic with Merlin: Formatting numbers and currency
Discusscode85 KBe-mail it!
Contents:
The root of it all: NumberFormat
Learning to iterate through the characters in DecimalFormat
Deciding on messages based on a value range and ChoiceFormat
Counting our money with Currency
Summary
Resources
About the author
Rate this article
Related content:
Magic with Merlin series
Java internationalization basics
Unicode and software internationalization
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Merlin makes formatting numbers and discovering local currency codes a snap

Level: Intermediate

John Zukowski (mailto:jaz@zukowski.net?cc=&subject=Formatting numbers and currency)
President, JZ Ventures, Inc.
13 August 2003

Column iconInternationalizing your applications requires both text messages and displayed numbers to be formatted for the appropriate language and style of the user's domain. With the Merlin release of the J2SE platform, formatting integers has gotten simpler, and you can now find out the ISO 4217 currency codes. In this month's Magic with Merlin, John Zukowski shows you how to format numbers and introduces the new support for discovering local currency codes. Share your thoughts on this article with the author and other readers in the accompanying discussion forum. (You can also click Discuss at the top or bottom of the article to access the forum.)

The java.text package allows you to format text messages, dates, and numbers in a manner independent of a specific language. Many people use resource bundles with the MessageFormat class to localize messages for users. Even more people seem to use the DateFormat and SimpleDateFormat classes for working with date strings, both for input and output. The least used of the bunch seems to be the NumberFormat class and its associated subclasses DecimalFormat and ChoiceFormat. This month, we'll take a look at these three underused classes, plus the Currency class, to see just how worldly J2SE 1.4 has become.

The root of it all: NumberFormat
If you're from the United States, you place a comma in large numbers to designate thousands and millions (and so on up for every three digits). For floating-point numbers, you place a decimal as the separator between the whole part of the number and the fraction. With money, the currency symbol is $, placed before the amount. If you never travel outside the States or never expect your programs to for that matter, you might not care about formatting currencies in yens (¥) for Japan, pounds (£) for Great Britain, or euros (€) for most of the rest of Europe.

For those who do care, there's NumberFormat and its related classes. Developers use the NumberFormat class to read numbers entered by users and format output to be displayed for users to see.

Similar to DateFormat, the NumberFormat class is abstract. You never create an instance of it -- instead, you always work with a subclass. While you can create a subclass directly through the constructor for the subclass, the NumberFormat class offers a series of getXXXInstance() methods to get locale-specific versions for different types of number classes. There are five such methods available:

"You say tomato," . . .
Locales represent a language or country-specific representation of how things are done, either for text messages or number formats typically. For instance, in the United States people use the word "color," while in the United Kingdom, it is "colour." The java.util.Locale class is used to represent the different locales.

For an excellent introduction to internationalization using the Java language, you cannot do better than Joe Sam Shirah's "Java internationalization basics" tutorial, which covers I18N in great detail.

  • getCurrencyInstance()
  • getInstance()
  • getIntegerInstance()
  • getNumberInstance()
  • getPercentInstance()

Which one you use depends on what type of number you want to display (or accept for input). Each method offers two versions -- one that works for the current locale and one that accepts a Locale as an argument to possibly specify a different locale.

New to J2SE 1.4 with NumberFormat are the getIntegerInstance(), getCurrency(), and setCurrency() methods. Let's see how to use NumberFormat by examining the new getIntegerInstance() method. We'll explore the get/set currency methods later.

The basic process of using NumberFormat is to get an instance and use it. Picking the right instance does require some thought. Typically, you don't want to use the general getInstance() or getNumberInstance() versions because you don't know exactly what you are going to get. Instead, you'd use something like getIntegerInstance() because you know you want to display something as an integer, without any decimal amount. This is demonstrated in Listing 1, where we display the number 54321 in formats appropriate for the United States and Germany:

Listing 1. Working with NumberFormat

import java.text.*;
import java.util.*;

public class IntegerSample {
  public static void main(String args[]) {
    int amount = 54321;
    NumberFormat usFormat = 
      NumberFormat.getIntegerInstance(Locale.US);
    System.out.println(usFormat.format(amount));
    NumberFormat germanFormat = 
      NumberFormat.getIntegerInstance(Locale.GERMANY);
    System.out.println(germanFormat.format(amount));
  }
}

Running the code produces the output in Listing 2. Notice the comma for the first (U.S.) format and the period separator for the second (German) formatted number.

Listing 2. NumberFormat output

54,321
54.321

Learning to iterate through the characters in DecimalFormat
While NumberFormat is abstract and you work with instances of it through various methods like getIntegerInstance(), the DecimalFormat class offers a concrete version of that class. You can explicitly specify the character pattern for how you want positive, negative, fractional, and exponential numbers to appear. If you don't like the predefined formats for the different locales, you can create your own. (Under the covers, DecimalFormat is probably what NumberFormat is using.) The basic DecimalFormat functionality hasn't changed with the 1.4. release of the J2SE platform. What has changed is the addition of the formatToCharacterIterator(), getCurrency(), and setCurrency() methods.

We'll take a quick look at the new formatToCharacterIterator method and its associated NumberFormat.Field class. J2SE 1.4 introduces the concept of a CharacterIterator, which allows you to traverse bi-directionally through text. With formatToCharacterIterator, you get its subinterface AttributedCharacterIterator, which allows you to find out information about that text. In the case of DecimalFormat, those attributes are keys from NumberFormat.Field. By using AttributedCharacterIterator, you literally can build up your own string output from the generated results. Listing 3 uses a percent instance to show you a simple demonstration:

Listing 3. Working with formatToCharacterIterator()

import java.text.*;
import java.util.*;

public class DecimalSample {
  public static void main(String args[]) {
    double amount = 50.25;
    NumberFormat usFormat = NumberFormat.getPercentInstance(Locale.US);
    if (usFormat instanceof DecimalFormat) {
      DecimalFormat decFormat = (DecimalFormat)usFormat;
      AttributedCharacterIterator iter =
        decFormat.formatToCharacterIterator(new Double(amount));
        for (char c = iter.first();
            c != CharacterIterator.DONE;
            c = iter.next()) {
          // Get map for current character
          Map map = iter.getAttributes();
          // Display its attributes
          System.out.println("Char: " + c + " / " + map);
     }
    }
  }
}

Listing 4 shows the program's output (after a little massaging to make it more readable). Basically, the formatToCharacterIterator() method works the same way as calling format(), but in addition to formatting the output string, it tags each character in the output with its attributes (for instance, is the character at position X an integer?). The output of displaying 50.25 as a percentage is "5,025%" for the U.S. locale. By examining the output, everything but the "%" is an "integer," including the comma. Besides the numbers, the comma is also flagged as a grouping separator and the percent sign is flagged as a percent. The attributes of each digit are a java.util.Map, in which each attribute is shown as key=value, where both are the same. In the case of a comma where multiple attributes are present, the list is comma separated.

Listing 4. formatToCharacterIterator() output

Char: 5 / {java.text.NumberFormat$Field(integer)=
  java.text.NumberFormat$Field(integer)}
Char: , / {java.text.NumberFormat$Field(grouping separator)=
  java.text.NumberFormat$Field(grouping separator),
  java.text.NumberFormat$Field(integer)=
  java.text.NumberFormat$Field(integer)}
Char: 0 / {java.text.NumberFormat$Field(integer)=
  java.text.NumberFormat$Field(integer)}
Char: 2 / {java.text.NumberFormat$Field(integer)=
  java.text.NumberFormat$Field(integer)}
Char: 5 / {java.text.NumberFormat$Field(integer)=
  java.text.NumberFormat$Field(integer)}
Char: % / {java.text.NumberFormat$Field(percent)=
  java.text.NumberFormat$Field(percent)}

Deciding on messages based on a value range and ChoiceFormat
ChoiceFormat is another of those concrete subclasses of NumberFormat. Its definition and behavior haven't changed with the 1.4 release. ChoiceFormat doesn't really help you format numbers, but it does allow you to customize the text associated with a value. In the simplest case, imagine displaying an error message. If there is a single reason for failure, you want to use the word "is." If there are two or more reasons, you want to use the word "are." As Listing 5 illustrates, ChoiceFormat allows you to map a range of values to different text strings.

The ChoiceFormat class is typically used with the MessageFormat class to produce concatenated messages in a language-neutral format. Not shown here is the use of a ResourceBundle (which is typically used with ChoiceFormat) to get those strings. See Resources for information on how to work with resource bundles; specifically, the "Java internationalization basics" tutorial provides an in-depth discussion.

Listing 5. Working with ChoiceFormat

import java.text.*;
import java.util.*;

public class ChoiceSample {
  public static void main(String args[]) {
    double limits[] = {0, 1, 2};
    String messages[] = {
      "is no content",
      "is one item",
      "are many items"};
    ChoiceFormat formats = new ChoiceFormat(limits, messages);
    MessageFormat message = new MessageFormat("There {0}.");
    message.setFormats(new Format[]{formats});
    for (int i=0; i<5; i++) {
      Object formatArgs[] = {new Integer(i)};
      System.out.println(i + ": " + message.format(formatArgs));
    }
  }
}   

Executing the program produces the output shown in Listing 6:

Listing 6. ChoiceFormat output

0: There is no content.
1: There is one item.
2: There are many items.
3: There are many items.
4: There are many items.

Counting our money with Currency
The previously mentioned getCurrency() and setCurrency() methods return an instance of the new java.util.Currency class. This class provides access to the ISO 4217 currency codes for various countries. While you've been able to use getCurrencyInstance() with NumberFormat since it was introduced, you could never get or display the different currency symbols for a locale apart from their numeric display. With the new Currency class, you easily can.

As previously mentioned, currency codes come from ISO 4217. By passing in either the Locale for a country or the actual alphabetic code for the currency, Currency.getInstance() will give you back a valid Currency object. The getCurrency() method of NumberFormat will do the same after creating a currency instance for a specific locale. Listing 7 shows how to get a currency instance and how to format a number to be displayed as currency. Remember that these conversions are for display only. If you need to convert amounts between currencies, do so before figuring out how to display the values.

Listing 7. Working with getCurrencyInstance() and Currency

import java.text.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;

public class CurrencySample {
  public static void main(String args[]) {
     StringBuffer buffer = new StringBuffer(100);
    Currency dollars = Currency.getInstance("USD");
    Currency pounds = Currency.getInstance(Locale.UK);
    buffer.append("Dollars: ");
    buffer.append(dollars.getSymbol());
    buffer.append("\n");
    buffer.append("Pound Sterling: ");
    buffer.append(pounds.getSymbol());
    buffer.append("\n-----\n");
    double amount = 5000.25;
    NumberFormat usFormat = NumberFormat.getCurrencyInstance(Locale.US);
    buffer.append("Symbol: ");
    buffer.append(usFormat.getCurrency().getSymbol());
    buffer.append("\n");
    buffer.append(usFormat.format(amount));
    buffer.append("\n");
    NumberFormat germanFormat = 
      NumberFormat.getCurrencyInstance(Locale.GERMANY);
    buffer.append("Symbol: ");
    buffer.append(germanFormat.getCurrency().getSymbol());
    buffer.append("\n");
    buffer.append(germanFormat.format(amount));
    JFrame frame = new JFrame("Currency");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JTextArea ta = new JTextArea(buffer.toString());
    JScrollPane pane = new JScrollPane(ta);
    frame.getContentPane().add(pane, BorderLayout.CENTER);
    frame.setSize(200, 200);
    frame.show();
  }
}       

Unfortunately, the currency symbols returned for things like euros and pounds are not the actual symbols, but are instead the three-digit currency code (from ISO 4217). However, with getCurrencyInstance(), the symbol is displayed, as shown in Figure 1.

Figure 1. Seeing the currency symbols
Seeing the Currency Symbols

Summary
There is more to globalizing your programs than just customizing the text messages. While moving text messages into resource bundles is at least half the work, don't forget about dealing with locale-centric numbers and currency displays, too. Not everyone uses commas and periods in the same way as in the United States for numeric display, and everyone has to deal with their own currency specifics. While we don't have to rely on the old COBOL picture strings like $$$.99, by using locale-specific NumberFormat instances, you can make your programs more internationalized.

Resources

About the author
John Zukowski conducts strategic Java consulting with JZ Ventures, Inc., offers technical support through AnswerSquad.com, and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Mastering Java 2, J2SE 1.4 (Sybex, April 2002) and Learn Java with JBuilder 6 (Apress, March 2002). Contact John at mailto:jaz@zukowski.net?Subject=Magic with Merlin.


Discusscode85 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Send us your comments or click Discuss to share your comments with others.



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact