Um Guia Prático para DecimalFormat

Um Guia Prático para DecimalFormat

1. Visão geral

Neste artigo, vamos explorar a classeDecimalFormat junto com seus usos práticos.

Esta é uma subclasse deNumberFormat, que permite formatar a representação deString de números decimais usando padrões predefinidos.

Também pode ser usado inversamente, para analisar Strings em números.

2. Como funciona?

Para formatar um número, precisamos definir um padrão, que é uma sequência de caracteres especiais potencialmente misturados ao texto.

Existem 11 caracteres de padrão especial, mas os mais importantes são:

  • 0 - imprime um dígito, se houver, 0 caso contrário

  • # - imprime um dígito, se houver, nada diferente

  • . - indicar onde colocar o separador decimal

  • , - indique onde colocar o separador de agrupamento

Quando o padrão é aplicado a um número, suas regras de formatação são executadas e o resultado é impresso de acordo comDecimalFormatSymbol deLocale de nossa JVM, a menos que umLocale específico seja especificado.

As saídas dos exemplos a seguir são de uma JVM em execução em umLocale inglês.

3. Formatação básica

Vamos agora ver quais saídas são produzidas ao formatar o mesmo número com os seguintes padrões.

3.1. Decimais Simples

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

Como podemos ver, a parte inteira nunca é descartada, não importa se o padrão é menor que o número.

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

Se o padrão for maior que o número, zeros serão adicionados, enquanto os hashes serão descartados, tanto no número inteiro quanto nas casas decimais.

3.2. Arredondamento

Se a parte decimal do padrão não pode conter toda a precisão do número de entrada, ele será arredondado.

Aqui, a parte .89 foi arredondada para 0,90, depois o 0 foi descartado:

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

Aqui, a parte .89 foi arredondada para 1,00, depois a .00 foi descartada e a 1 foi somada à 7:

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

O modo de arredondamento padrão éHALF_EVEN,, mas pode ser personalizado por meio do métodosetRoundingMode.

3.3. Agrupamento

O separador de agrupamento é usado para especificar um subpadrão que é repetido automaticamente:

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

3.4. Vários padrões de agrupamento

Alguns países têm um número variável de padrões de agrupamento em seus sistemas de numeração.

O sistema de numeração indiano usa o formato,,. ##, no qual apenas o primeiro separador de agrupamento contém três números, enquanto todos os outros contêm dois números.

Isso não é possível com o uso da classeDecimalFormat, que mantém apenas o último padrão encontrado da esquerda para a direita e o aplica ao número inteiro, ignorando os padrões de agrupamento anteriores.

Uma tentativa de usar o padrão,,,, resultaria em um reagrupamento para,e terminaria em uma redistribuição para,,, ##.

Para alcançar a correspondência de vários padrões de agrupamento, é necessário escrever nosso próprio código de manipulaçãoString ou, alternativamente, tentar oIcu4J’s DecimalFormat, que permite isso.

3.5. Misturando Literais de Cordas

É possível misturar literaisString dentro do padrão:

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

Também é possível usar caracteres especiais como literaisString, por meio de escape:

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

4. Formatação Localizada

Muitoscountries não usam símbolos em inglês e usam a vírgula como separador decimal e o ponto como separador de agrupamento.

Executar o padrão,. em uma JVM com umLocale italiano, por exemplo, produziria 1.234.567,89.

Embora esse possa ser um recurso útil do i18n em alguns casos, em outros, podemos querer impor um formato específico, independente da JVM.

Veja como podemos fazer isso:

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

Se oLocale em que estamos interessados ​​não estiver entre aqueles cobertos pelo construtorDecimalFormatSymbols, podemos especificá-lo com o métodogetInstance:

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

5. Notações Científicas

OScientific Notation representa o produto de uma Mantissa e um expoente de dez. O número 1234567.89 também pode ser representado como 12.3456789 * 10 ^ 5 (o ponto é deslocado em 5 posições).

5.1. E-Notação

É possível expressar um número em notação científica usando o caractere padrãoE que representa o expoente de dez:

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

Devemos ter em mente que o número de caracteres após o expoente é relevante, portanto, se precisarmos expressar 10 ^ 12, precisaremos deE00e nãoE0.

5.2. Notação de Engenharia

É comum usar uma forma particular de Notação Científica chamada Notação de Engenharia, que ajusta os resultados para serem expressos como múltiplos de três, por exemplo, ao usar unidades de medida como Kilo (10 ^ 3), Mega (10 ^ 6), Giga ( 10 ^ 9) e assim por diante.

Podemos aplicar este tipo de notação ajustando o número máximo de dígitos inteiros (os caracteres expressos com o # e à esquerda do separador decimal) de forma que seja maior que o número mínimo (aquele expresso com 0) e maior que 1

Isso força o expoente a ser um múltiplo do número máximo, portanto, para este caso de uso, queremos que o número máximo seja três:

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

6. Análise

Vamos ver como é possível analisar umString em umNumber com o método de análise:

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

Uma vez que o valor retornado não é inferido pela presença de um separador decimal, podemos usar os métodos como.doubleValue(),.longValue() do objetoNumber retornado para impor uma primitiva específica na saída.

Também podemos obter umBigDecimal da seguinte forma:

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

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

7. Segurança de Thread

DecimalFormat isn’t thread-safe, portanto, devemos prestar atenção especial ao compartilhar a mesma instância entre threads.

8. Conclusão

Vimos os principais usos da classeDecimalFormat, junto com seus pontos fortes e fracos.

Como sempre, o código-fonte completo está disponívelover on Github.