Una guida pratica a DecimalFormat

1. Panoramica

In questo articolo, esploreremo la classe DecimalFormat insieme ai suoi usi pratici.

Questa è una sottoclasse di NumberFormat , che consente di formattare la rappresentazione String dei numeri decimali utilizzando modelli predefiniti.

Può anche essere usato inversamente, per analizzare le stringhe in numeri.

2. Come funziona?

Per formattare un numero, dobbiamo definire un pattern, che è una sequenza di caratteri speciali potenzialmente mescolati con il testo.

Ci sono 11 caratteri di pattern speciali, ma i più importanti sono:

  • 0 - stampa una cifra se fornita, 0 altrimenti
  • # - stampa una cifra se fornita, nient'altro
  • . - indicare dove mettere il separatore decimale
  • , - indicare dove mettere il separatore di raggruppamento

Quando il modello viene applicato a un numero, le sue regole di formattazione vengono eseguite, e il risultato viene stampato in base alla DecimalFormatSymbol della nostra JVM Locale a meno che una specifica Locale è specificato.

Uscite I seguenti esempi sono da una JVM in esecuzione su un inglese Locale .

3. Formattazione di base

Vediamo ora quali output vengono prodotti quando si formatta lo stesso numero con i seguenti modelli.

3.1. Decimali semplici

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

Come possiamo vedere, la parte intera non viene mai scartata, non importa se il pattern è più piccolo del numero.

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

Se invece il pattern è più grande del numero, vengono aggiunti degli zeri, mentre gli hash vengono eliminati, sia nella parte intera che in quella decimale.

3.2. Arrotondamento

Se la parte decimale del modello non può contenere l'intera precisione del numero di input, viene arrotondata.

Qui, la parte .89 è stata arrotondata a .90, quindi lo 0 è stato eliminato:

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

Qui, la parte .89 è stata arrotondata a 1.00, quindi .00 è stata eliminata e l'1 è stato sommato a 7:

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

La modalità di arrotondamento predefinita è HALF_EVEN, ma può essere personalizzata tramite il metodo setRoundingMode .

3.3. Raggruppamento

Il separatore di raggruppamento viene utilizzato per specificare un sotto-modello che viene ripetuto automaticamente:

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

3.4. Modelli di raggruppamento multipli

Alcuni paesi hanno un numero variabile di modelli di raggruppamento nei loro sistemi di numerazione.

Il sistema di numerazione indiano utilizza il formato #, ##, ###. ##, in cui solo il primo separatore di raggruppamento contiene tre numeri, mentre tutti gli altri contengono due numeri.

Ciò non è possibile ottenere utilizzando la classe DecimalFormat , che mantiene solo l'ultimo modello incontrato da sinistra a destra e lo applica all'intero numero, ignorando i precedenti modelli di raggruppamento.

Un tentativo di utilizzare il modello #, ##, ##, ##, ### comporterebbe un raggruppamento in #######, ### e finire con una ridistribuzione in #, ###, # ##, ###.

Per ottenere più corrispondenze di modelli di raggruppamento, è necessario scrivere il nostro codice di manipolazione delle stringhe o, in alternativa, provare DecimalFormat di Icu4J , che lo consente.

3.5. Mescolando stringhe letterali

È possibile mescolare stringhe letterali all'interno del pattern:

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

È anche possibile utilizzare caratteri speciali come stringhe letterali, tramite l'escape:

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

4. Formattazione localizzata

Molti paesi non utilizzano simboli inglesi e utilizzano la virgola come separatore decimale e il punto come separatore di raggruppamento.

L'esecuzione del pattern #, ###. ## su una JVM con impostazioni internazionali italiane , ad esempio, produrrebbe 1.234.567,89.

Sebbene in alcuni casi questa potrebbe essere un'utile funzionalità i18n, in altri potremmo voler applicare un formato specifico, indipendente dalla JVM.

Ecco come possiamo farlo:

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 la Locale che ci interessa non è tra quelle coperte dal costruttore DecimalFormatSymbols , possiamo specificarla con il metodo getInstance :

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

5. Notazioni scientifiche

La notazione scientifica rappresenta il prodotto di una mantissa e di un esponente di dieci. Il numero 1234567.89 può anche essere rappresentato come 12.3456789 * 10 ^ 5 (il punto è spostato di 5 posizioni).

5.1. E -Notation

È possibile esprimere un numero in notazione scientifica utilizzando il carattere modello E che rappresenta l'esponente di dieci:

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

Dovremmo tenere a mente che il numero di caratteri dopo l'esponente è rilevante, quindi se dobbiamo esprimere 10 ^ 12, abbiamo bisogno di E00 e non E0 .

5.2. Notazione tecnica

È comune utilizzare una particolare forma di notazione scientifica chiamata Notazione ingegneristica, che regola i risultati in modo da essere espressi come multipli di tre, ad esempio quando si utilizzano unità di misura come Kilo (10 ^ 3), Mega (10 ^ 6), Giga ( 10 ^ 9) e così via.

Possiamo applicare questo tipo di notazione regolando il numero massimo di cifre intere (i caratteri espressi con il # ea sinistra del separatore decimale) in modo che sia maggiore del numero minimo (quello espresso con lo 0) e maggiore di 1.

Questo forza l'esponente a essere un multiplo del numero massimo, quindi per questo caso d'uso vogliamo che il numero massimo sia tre:

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

6. Analisi

Vediamo come è possibile analizzare una stringa in un numero con il metodo 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);

Poiché il valore restituito non è dedotto dalla presenza di un separatore decimale, possiamo utilizzare metodi come .doubleValue () , .longValue () dell'oggetto Number restituito per applicare una specifica primitiva in output.

Possiamo anche ottenere un BigDecimal come segue:

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

DecimalFormat non è thread-safe , quindi dovremmo prestare particolare attenzione quando condividiamo la stessa istanza tra thread.

8. Conclusione

Abbiamo visto i principali utilizzi della classe DecimalFormat , insieme ai suoi punti di forza e di debolezza .

Come sempre, il codice sorgente completo è disponibile su Github.