Internationalization with Numbers in Java

Naveen AnamaneniUpdated · 5 min read

The number one thousand two hundred thirty-four and a half is written 1,234.5 in the United States and 1.234,5 in Germany. The comma and the period swap roles. A program that prints or reads numbers with fixed symbols will mislead half its users, sometimes by a factor of a thousand. Java's NumberFormat class formats and parses numbers according to a Locale: it knows each region's grouping separator, decimal separator, percent style, and even which digits to use. In this lesson, you will learn its factory methods, how to control decimals, and why parsing must use the right locale.

"NumberFormat and DecimalFormat in Java" introduced the class; this lesson concentrates on locales.

Methods

MethodDescription
NumberFormat.getNumberInstance(locale)It is used to format ordinary numbers.
NumberFormat.getIntegerInstance(locale)It formats whole numbers, rounding fractions.
NumberFormat.getPercentInstance(locale)It formats fractions as percentages: 0.856 becomes 86%.
NumberFormat.getCompactNumberInstance(locale, style)It formats large numbers briefly, such as 2M.
NumberFormat.getCurrencyInstance(locale)It formats money (see "Internationalization with Currency in Java").
format(number)It turns a number into text.
parse(text)It turns text into a Number; throws ParseException if it can't.
setMinimumFractionDigits(n), setMaximumFractionDigits(n)They control how many decimals are shown.
setGroupingUsed(boolean)It turns the thousands separator on or off.

Example: Numbers, Percentages and Compact Forms

Numbers.java▶ Try it
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
 
public class Numbers {
    public static void main(String[] args) throws ParseException {
        double value = 1234567.891;
        Locale[] locales = {Locale.US, Locale.GERMANY, Locale.FRANCE,
            Locale.of("en", "IN"), Locale.of("hi", "IN"),
            Locale.forLanguageTag("hi-IN-u-nu-deva")};
 
        for (Locale locale : locales) {
            NumberFormat number = NumberFormat.getNumberInstance(locale);
            NumberFormat percent = NumberFormat.getPercentInstance(locale);
            NumberFormat compact = NumberFormat.getCompactNumberInstance(
                locale, NumberFormat.Style.SHORT);
            System.out.println(locale.toLanguageTag() + "  "
                + number.format(value) + "  |  " + percent.format(0.856)
                + "  |  " + compact.format(1_500_000));
        }
 
        NumberFormat twoPlaces = NumberFormat.getNumberInstance(Locale.US);
        twoPlaces.setMinimumFractionDigits(2);
        twoPlaces.setMaximumFractionDigits(2);
        System.out.println(twoPlaces.format(91) + " and "
            + twoPlaces.format(84.256));
 
        // The same text means different numbers in different locales.
        String text = "1.234";
        System.out.println("US reads \"1.234\" as "
            + NumberFormat.getNumberInstance(Locale.US).parse(text));
        System.out.println("Germany reads \"1.234\" as "
            + NumberFormat.getNumberInstance(Locale.GERMANY).parse(text));
    }
}
Output
en-US  1,234,567.891  |  86%  |  2M
de-DE  1.234.567,891  |  86 %  |  2 Mio.
fr-FR  1 234 567,891  |  86 %  |  2 M
en-IN  1,234,567.891  |  86%  |  15L
hi-IN  1,234,567.891  |  86%  |  15 लाख
hi-IN-u-nu-deva  १,२३४,५६७.८९१  |  ८६%  |  १५ लाख
91.00 and 84.26
US reads "1.234" as 1.234
Germany reads "1.234" as 1234

Explanation:

  1. Separators: the US writes 1,234,567.891; Germany swaps the two symbols; France groups with a narrow no-break space and uses a decimal comma.
  2. Percent: 0.856 is multiplied by 100 and rounded. Germany and France put a space before the sign.
  3. Compact numbers shorten 1,500,000. English gives "2M" (rounded to a whole number by default), German "2 Mio.", and the Indian locales "15L" and "15 लाख", because India counts in lakhs (1 lakh = 100,000).
  4. Digits: the last locale, hi-IN-u-nu-deva, asks for Devanagari digits through a locale extension. The same number appears as १,२३४,५६७.८९१.
  5. Fraction digits: with minimum and maximum set to 2, 91 prints as 91.00 and 84.256 rounds to 84.26.
  6. Parsing: the text 1.234 is the number 1.234 to a US reader and 1234 to a German reader. Parsing with the wrong locale gives a wrong number with no error.

Numbers for People vs Numbers for Programs

ReaderUse
A personNumberFormat with that person's locale
A program (JSON, CSV, a database, a URL)String.valueOf, Double.toString, BigDecimal.toPlainString(), and Double.parseDouble to read. These always use a period and no grouping.

Methods that take a format string follow the default locale: String.format("%,.2f", x) prints a decimal comma on a German system. For data, pass Locale.ROOT as the first argument.

Common Mistakes

Parsing user input with Double.parseDouble. It accepts only the plain form with a period; "1.234,5" fails, and "1,234" fails too.

Writing data files with the default locale. A file written on a German machine can't be read on an American one.

Assuming three-digit groups. Grouping rules belong to the locale data; never insert separators by hand.

Ignoring what parse accepted. It stops at the first character it doesn't understand; "12abc" parses as 12. Use parse(text, position) and check the position when the whole text must be a number.

Practice

  1. Easy: Format 0.075 as a percentage with one decimal place for Locale.US and Locale.GERMANY.
  2. Harder: Write a method that parses a number typed by a user in a given locale and rejects input with leftover characters, using ParsePosition.

Quick check: Numbers and locales

4 questions
1How does Germany write one thousand two hundred thirty-four and a half?
2What does getPercentInstance print for 0.856 in the US?
3What is the number "1.234" when parsed with Locale.GERMANY?
4Which should write a number into a JSON file?

Key Takeaways

Was this tutorial helpful?

Lesson 507 of 578