Автор оригинала: 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 .