A Practical Guide to DecimalFormat – 十进制格式的实用指南

最后修改: 2018年 2月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’re going to explore the DecimalFormat class along with its practical usages.

在这篇文章中,我们将探讨DecimalFormat类以及它的实际用途。

This is a subclass of NumberFormat, which allows formatting decimal numbers’ String representation using predefined patterns.

这是NumberFormat的一个子类,它允许使用预定义的模式来格式化十进制数字的String表示。

It can also be used inversely, to parse Strings into numbers.

它也可以反过来使用,将字符串解析为数字。

2. How Does It Work?

2.它是如何工作的?

In order to format a number, we have to define a pattern, which is a sequence of special characters potentially mixed with text.

为了格式化一个数字,我们必须定义一个模式,这是一个可能与文本混合的特殊字符的序列。

There are 11 Special Pattern Characters, but the most important are:

有11个特殊模式字符,但最重要的是。

  • 0 – prints a digit if provided, 0 otherwise
  • # – prints a digit if provided, nothing otherwise
  • . – indicate where to put the decimal separator
  • , – indicate where to put the grouping separator

When the pattern gets applied to a number, its formatting rules are executed, and the result is printed according to the DecimalFormatSymbol of our JVM’s Locale unless a specific Locale is specified.

当模式被应用于一个数字时,它的格式化规则被执行,结果根据我们JVM的DecimalFormatSymbolLocale打印,除非指定了一个特定的Locale

The following examples’ outputs are from a JVM running on an English Locale.

以下例子的输出来自运行在英语Locale的JVM。

3. Basic Formatting

3.基本格式化

Let’s now see which outputs are produced when formatting the same number with the following patterns.

现在让我们看看用以下模式对同一个数字进行格式化时,会产生哪些输出。

3.1. Simple Decimals

3.1.简单的小数

double d = 1234567.89;    
assertThat(
  new DecimalFormat("#.##").format(d)).isEqualTo("1234567.89");
assertThat(
  new DecimalFormat("0.00").format(d)).isEqualTo("1234567.89");

As we can see, the integer part is never discarded, no matter if the pattern is smaller than the number.

我们可以看到,无论图案是否小于数字,整数部分都不会被丢弃。

assertThat(new DecimalFormat("#########.###").format(d))
  .isEqualTo("1234567.89");
assertThat(new DecimalFormat("000000000.000").format(d))
  .isEqualTo("001234567.890");

If the pattern instead is bigger than the number, zeros get added, while hashes get dropped, both in the integer and in the decimal parts.

如果图案比数字大,就会加零,而哈希值会被删除,包括整数和小数部分。

3.2. Rounding

3.2.四舍五入

If the decimal part of the pattern can’t contain the whole precision of the input number, it gets rounded.

如果模式的小数部分不能包含输入数字的整个精度,它就会被四舍五入。

Here, the .89 part has been rounded to .90, then the 0 has been dropped:

这里,0.89的部分被四舍五入为0.90,然后0被删除。

assertThat(new DecimalFormat("#.#").format(d))
  .isEqualTo("1234567.9");

Here, the .89 part has been rounded to 1.00, then the .00 has been dropped and the 1 has been summed to the 7:

这里,0.89的部分被四舍五入为1.00,然后0.00被删除,1被加到7。

assertThat(new DecimalFormat("#").format(d))
  .isEqualTo("1234568");

The default rounding mode is HALF_EVEN, but it can be customized through the setRoundingMode method.

默认的四舍五入模式是HALF_EVEN,但可以通过setRoundingMode方法进行自定义。

3.3. Grouping

3.3.分组

The grouping separator is used to specify a sub-pattern which gets repeated automatically:

分组分隔符用于指定一个自动重复的子模式。

assertThat(new DecimalFormat("#,###.#").format(d))
  .isEqualTo("1,234,567.9");
assertThat(new DecimalFormat("#,###").format(d))
  .isEqualTo("1,234,568");

3.4. Multiple Grouping Patterns

3.4.多种分组模式

Some countries have a variable number of grouping patterns in their numbering systems.

一些国家在其编号系统中拥有数量不等的分组模式。

The Indian Numbering System uses the format #,##,###.##, in which only the first grouping separator holds three numbers, while all the others hold two numbers.

印度编号系统使用的格式是#,##,##.##,其中只有第一个分组分隔符有三个数字,而其他都有两个数字。

This isn’t possible to achieve using the DecimalFormat class, which keeps only the latest pattern encountered from left to right, and applies it to the whole number, ignoring previous grouping patterns.

使用DecimalFormat类是不可能实现的,它从左到右只保留最新遇到的模式,并将其应用于整个数字,忽略之前的分组模式。

An attempt to use the pattern #,##,##,##,### would result in a regroup to #######,### and end in a redistribution to #,###,###,###.

试图使用#,##,##,##,##的模式会导致重新分组到#######,##,并以重新分配到#,##,##,##结束。

To achieve multiple grouping pattern matching, it’s necessary to write our own String manipulation code, or alternatively to try the Icu4J’s DecimalFormat, which allows that.

为了实现多组模式匹配,有必要编写我们自己的String操作代码,或者尝试Icu4J的DecimalFormat,它允许这样做。

3.5. Mixing String Literals

3.5.混合字符串字数

It’s possible to mix String literals within the pattern:

有可能在模式内混合使用String字样。

assertThat(new DecimalFormat("The # number")
  .format(d))
  .isEqualTo("The 1234568 number");

It’s also possible to use special characters as String literals, through escaping:

也可以通过转义将特殊字符作为String字面意义使用。

assertThat(new DecimalFormat("The '#' # number")
  .format(d))
  .isEqualTo("The # 1234568 number");

4. Localized Formatting

4.本地化的格式化

Many countries don’t use English symbols and use the comma as decimal separator and the dot as grouping separator.

许多国家不使用英文符号,使用逗号作为小数点的分隔符,使用点作为分组的分隔符。

Running the #,###.## pattern on a JVM with an Italian Locale, for example, would output 1.234.567,89.

例如,在一个具有意大利Locale的JVM上运行#,##.##模式,将输出1.234.567,89。

While this could be a useful i18n feature in some cases, in others we might want to enforce a specific, JVM-independent format.

虽然在某些情况下,这可能是一个有用的i18n功能,但在其他情况下,我们可能想强制执行一个特定的、与JVM无关的格式。

Here’s how we can do that:

下面是我们如何做到这一点。

assertThat(new DecimalFormat("#,###.##", 
  new DecimalFormatSymbols(Locale.ENGLISH)).format(d))
  .isEqualTo("1,234,567.89");
assertThat(new DecimalFormat("#,###.##", 
  new DecimalFormatSymbols(Locale.ITALIAN)).format(d))
  .isEqualTo("1.234.567,89");

If the Locale we’re interested in is not among the ones covered by the DecimalFormatSymbols constructor, we can specify it with the getInstance method:

如果我们感兴趣的Locale不在DecimalFormatSymbols构造函数所涵盖的范围内,我们可以通过getInstance方法指定它。

Locale customLocale = new Locale("it", "IT");
assertThat(new DecimalFormat(
  "#,###.##", 
   DecimalFormatSymbols.getInstance(customLocale)).format(d))
  .isEqualTo("1.234.567,89");

5. Scientific Notations

5.科学记数法

The Scientific Notation represents the product of a Mantissa and an exponent of ten. The number 1234567.89 can also be represented as 12.3456789 * 10^5 (the dot is shifted by 5 positions).

科学记数法表示一个尾数和一个十的指数的乘积。数字1234567.89也可以表示为12.3456789 * 10^5(点移了5个位置)。

5.1. E-Notation

5.1.E-注解

It’s possible to express a number in Scientific Notation using the E pattern character representing the exponent of ten:

可以使用代表十的指数的E模式字符在科学记数法中表达一个数字。

assertThat(new DecimalFormat("00.#######E0").format(d))
  .isEqualTo("12.3456789E5");
assertThat(new DecimalFormat("000.000000E0").format(d))
  .isEqualTo("123.456789E4");

We should keep in mind that the number of characters after the exponent is relevant, so if we need to express 10^12, we need E00 and not E0.

我们应该记住,指数后面的字符数是相关的,所以如果我们需要表达10^12,我们需要E00而不是E0

5.2. Engineering Notation

5.2.工程记号

It’s common to use a particular form of Scientific Notation called Engineering Notation, which adjusts results in order to be expressed as multiple of three, for example when using measuring units like Kilo (10^3), Mega (10^6), Giga (10^9), and so on.

通常使用一种特殊形式的科学记数法,称为工程记数法,它将结果调整为3的倍数来表示,例如在使用基洛(10^3)、兆(10^6)、吉加(10^9)等测量单位时。

We can enforce this kind of notation by adjusting the maximum number of integer digits (the characters expressed with the # and on the left of the decimal separator) so that it’s higher than the minimum number (the one expressed with the 0) and higher than 1.

我们可以通过调整最大的整数位数(用#表示的字符,在小数点分隔符的左边),使其高于最小的数字(用0表示的数字),并高于1,来执行这种记数法。

This forces the exponent to be a multiple of the maximum number, so for this use-case we want the maximum number to be three:

这就迫使指数成为最大数字的倍数,所以在这个使用案例中,我们希望最大数字是3。

assertThat(new DecimalFormat("##0.######E0")
  .format(d)).isEqualTo("1.23456789E6");		
assertThat(new DecimalFormat("###.000000E0")
  .format(d)).isEqualTo("1.23456789E6");

6. Parsing

6.解析

Let’s see how is possible to parse a String into a Number with the parse method:

让我们看看如何用parse方法将一个字符串解析成一个数字

assertThat(new DecimalFormat("", new DecimalFormatSymbols(Locale.ENGLISH))
  .parse("1234567.89"))
  .isEqualTo(1234567.89);
assertThat(new DecimalFormat("", new DecimalFormatSymbols(Locale.ITALIAN))
  .parse("1.234.567,89"))
  .isEqualTo(1234567.89);

Since the returned value isn’t inferred by the presence of a decimal separator, we can use the methods like .doubleValue(), .longValue() of the returned Number object to enforce a specific primitive in output.

由于返回的值不是由小数点分隔符的存在来推断的,我们可以使用返回的.doubleValue(), .longValue()对象的方法来执行输出中的特定基数。

We can also obtain a BigDecimal as follows:

我们也可以通过以下方式获得BigDecimal

NumberFormat nf = new DecimalFormat(
  "", 
  new DecimalFormatSymbols(Locale.ENGLISH));
((DecimalFormat) nf).setParseBigDecimal(true);
 
assertThat(nf.parse("1234567.89"))
  .isEqualTo(BigDecimal.valueOf(1234567.89));

7. Thread-Safety

7.螺纹安全

DecimalFormat isn’t thread-safe, thus we should pay special attention when sharing the same instance between threads.

DecimalFormat不是线程安全的,因此当线程之间共享同一实例时,我们应该特别注意。

8. Conclusion

8.结论

We’ve seen the major usages of the DecimalFormat class, along with its strengths and weaknesses.

我们已经看到了DecimalFormat类的主要用途,以及它的优势和劣势.

As always, the full source code is available over on Github.

像往常一样,完整的源代码可在Github上获得