Рубрики
Без рубрики

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

Изучите класс DecimalFormat Java вместе с его практическим использованием.

Автор оригинала: Andrea Ligios.

1. Обзор

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

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

Он также может использоваться в обратном порядке, для разбора строк на числа.

2. Как Это Работает?

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

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

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

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

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

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. Округление

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

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

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

Здесь часть .89 была округлена до 1,00, затем .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 , который сохраняет только последний шаблон, встречающийся слева направо, и применяет его ко всему числу, игнорируя предыдущие шаблоны группировки.

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

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

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

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

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

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

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

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

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

Запуск шаблона#,###. # # на JVM с итальянским Locale , например, приведет к выводу 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");

Если Locale , который нас интересует, не входит в число тех, которые охватываются конструктором 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 , представляющий показатель степени десять:

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. Безопасность резьбы

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

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

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

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