Перейти к основному содержимому

Практическое руководство по десятичному формату

· 5 мин. чтения

1. Обзор

В этой статье мы собираемся изучить класс DecimalFormat и его практическое использование.

Это подкласс NumberFormat , который позволяет форматировать строковое представление десятичных чисел с использованием предопределенных шаблонов.

Его также можно использовать наоборот, чтобы разобрать строки на числа.

2. Как это работает?

Чтобы отформатировать число, мы должны определить шаблон, который представляет собой последовательность специальных символов, потенциально смешанных с текстом.

Существует 11 символов специального шаблона, но наиболее важными из них являются:

  • 0 — печатает цифру, если она указана, 0 в противном случае
  • — печатает цифру, если она указана, иначе ничего

  • . - указать, где ставить десятичный разделитель
  • , — указать, где поставить разделитель группировки

Когда шаблон применяется к числу, выполняются его правила форматирования, и результат печатается в соответствии с DecimalFormatSymbol нашего языкового стандарта JVM, если не указан конкретный языковой стандарт.

Выходные данные следующих примеров получены из JVM, работающей в английской локали .

3. Основное форматирование

Давайте теперь посмотрим, какие выходные данные получаются при форматировании одного и того же числа с помощью следующих шаблонов.

3.1. Простые десятичные дроби

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

Как мы видим, целая часть никогда не отбрасывается, даже если шаблон меньше числа.

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

Если вместо этого шаблон больше числа, добавляются нули, а хэши отбрасываются как в целых, так и в десятичных частях.

3.2. Округление

Если десятичная часть шаблона не может содержать всю точность введенного числа, она округляется.

Здесь часть 0,89 была округлена до 0,90, а затем отброшен 0:

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

Здесь часть 0,89 округляется до 1,00, затем 0,00 отбрасывается, а 1 суммируется с 7:

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

Режим округления по умолчанию — HALF_EVEN , но его можно настроить с помощью метода setRoundingMode .

3.3. Группировка

Разделитель группировки используется для указания подшаблона, который повторяется автоматически:

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

3.4. Несколько шаблонов группировки

Некоторые страны имеют переменное количество шаблонов группировки в своих системах нумерации.

Индийская система нумерации использует формат #,##,###.##, в котором только первый разделитель группировки содержит три числа, а все остальные содержат два числа.

Этого невозможно добиться с помощью класса DecimalFormat , который сохраняет только последний найденный шаблон слева направо и применяет его ко всему числу, игнорируя предыдущие шаблоны группировки.

Попытка использовать шаблон #,##,##,##,### приведет к перегруппировке в #######,### и закончится перераспределением в #,###,# ##,###.

Чтобы достичь множественного сопоставления шаблонов группировки, необходимо написать собственный код манипуляции со строками или, в качестве альтернативы, попробовать DecimalFormat от Icu4J , который это позволяет.

3.5. Смешивание строковых литералов

В шаблоне можно смешивать строковые литералы:

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

Также можно использовать специальные символы в качестве строковых литералов путем экранирования:

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

4. Локализованное форматирование

Многие страны не используют английские символы и используют запятую в качестве десятичного разделителя и точку в качестве разделителя групп.

Выполнение шаблона #,###.## на JVM с итальянской локалью , например, выведет 1.234.567,89.

Хотя в некоторых случаях это может быть полезной функцией i18n, в других мы можем захотеть применить определенный, независимый от JVM формат.

Вот как мы можем это сделать:

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");

Если интересующая нас локаль не входит в число поддерживаемых конструктором DecimalFormatSymbols , мы можем указать ее с помощью метода getInstance :

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

5. Научные обозначения

Научная нотация представляет собой произведение мантиссы и степени десяти. Число 1234567,89 также можно представить как 12,3456789 * 10^5 (точка сдвинута на 5 позиций).

5.1. E -Обозначение

Можно выразить число в научной нотации, используя символ шаблона E , представляющий показатель степени десяти:

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

Мы должны помнить, что количество символов после экспоненты имеет значение, поэтому, если нам нужно выразить 10 ^ 12, нам нужно E00 , а не E0 .

5.2. Инженерная нотация

Обычно используется особая форма научной нотации, называемая инженерной нотацией, которая корректирует результаты, чтобы они были кратны трем, например, при использовании таких единиц измерения, как килограмм (10 ^ 3), мега (10 ^ 6), гига ( 10^9) и так далее.

Мы можем применить этот вид записи, изменив максимальное количество целых цифр (символов, выраженных с помощью # и слева от десятичного разделителя), чтобы оно было выше минимального числа (тот, который выражен с помощью 0) и выше, чем 1.

Это заставляет показатель степени быть кратным максимальному числу, поэтому для этого варианта использования мы хотим, чтобы максимальное число было равно трем:

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

6. Разбор

Давайте посмотрим, как можно преобразовать строку в число с помощью метода 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);

Поскольку возвращаемое значение не определяется наличием десятичного разделителя, мы можем использовать такие методы, как .doubleValue() , .longValue() возвращаемого объекта Number , чтобы применить определенный примитив в выводе.

Мы также можем получить BigDecimal следующим образом:

NumberFormat nf = new DecimalFormat(
"",
new DecimalFormatSymbols(Locale.ENGLISH));
((DecimalFormat) nf).setParseBigDecimal(true);

assertThat(nf.parse("1234567.89"))
.isEqualTo(BigDecimal.valueOf(1234567.89));

7. Потокобезопасность

DecimalFormat не является потокобезопасным , поэтому мы должны уделять особое внимание совместному использованию одного и того же экземпляра между потоками.

8. Заключение

Мы рассмотрели основные варианты использования класса DecimalFormat , а также его сильные и слабые стороны .

Как всегда, полный исходный код доступен на Github .