Arrotondamento e troncamento in Java

DOMANDA:

Facendo le divisioni tra numeri float o double mi escono cifre decimali lunghissime. Come si arrotonda o si tronca un numero in Java?


RISPOSTA:

Un esempio in cui questo problema si presenta in modo massivo è durante lo sviluppo applicazioni che hanno a che fare con le valute.

Java mette a disposizione differenti metodi di arrotondamento e troncamento per soddisfare un po' tutte le esigenze.

Una possibile soluzione (parziale) è l'utilizzo della libreria java.lang.Math, in particolare dei suoi 4 metodi elencati di seguito (ricordiamo che Math contiene solo static):

FLOOR - double floor(double d):
questo metodo, come si evince dal nome "floor" (pavimento) arrotonda il numero alla cifra intera inferiore (il cosiddetto arrotondamento per difetto o troncamento).

ESEMPI:
double floor(4.4) -> restituisce 4.
double floor(4.6) -> restituisce 4.


CEIL - double ceil(double d):
questo metodo, al contrario di floor significa soffitto e, come intuibile, arrotonda il numero alla cifra intera superiore (arrotondamento per eccesso)

ESEMPI:
double ceil(4.4) -> restituisce 5.
double ceil(4.6) -> restituisce 5.


ROUND - int round(int i) oppure long round(double d):
questo metodo rappresenta l'arrotondamento forse più usato dall'uomo, il cosiddetto "arrotondamento matematico". In pratica arrotonda il numero all'intero più piccolo se la prima cifra decimale eliminata è minore/uguale a zero e all'intero più grande se è maggiore/uguale a 5.

ESEMPI:
double round(4.4) -> restituisce 4.
double round(4.5) -> restituisce 5.
double round(4.6) -> restituisce 5.


RINT - double rint(double d):
esattamente uguale a ROUND ma arrotonda al numero pari più vicino se è equidistante da due valori (cioè qualora fosse 5,5 o 6,5 arrotonda a 6, in caso 3,5 o 4,5 arrotonda  a 4).

ESEMPI:
double rint(4.4) -> restituisce 4.
double rint(4.5) -> restituisce 4 (pari più vicino).
double rint(4.6) -> restituisce 5.

double rint(5.4) -> restituisce 5.
double rint(5.5) -> restituisce 6 (pari più vicino).
double rint(5.6) -> restituisce 6.


Come anticipavo, questa soluzione è parziale. Infatti permette di arrotondare un numero a un intero. La nostra problematica era arrotondare i numeri decimali dopo la virgola.

Possiamo risolvere manualmente creandoci dei metodi ad hoc che sfruttino i quattro metodi appena visti. L'esempio di test qui di seguito ci permette di verificare anche il comportamento dei metodi precedenti:

package arrotondamenti;

import javax.swing.JOptionPane;

public class TestArrotondamenti {
   public static void main(String[] args) {
      // ATTENZIONE INSERIRE IL NUMERO DECIMALE CON IL PUNTO E NON CON LA VIRGOLA!!
      double arrotonda = Double.parseDouble(JOptionPane.showInputDialog("Inserisci il numero da arrotondare"));
      int cifre = Integer.parseInt(JOptionPane.showInputDialog("A quante cifre vuoi arrotondare?"));

      System.out.println(arrotonda + " PER DIFETTO (FLOOR) " + arrotondaPerDifetto(arrotonda, cifre));
      System.out.println(arrotonda + " PER ECCESSO (CEIL) " + arrotondaPerEccesso(arrotonda, cifre));
      System.out.println(arrotonda + " PER CLASSICO (ROUND) " + arrotonda(arrotonda, cifre));
      System.out.println(arrotonda + " AL PARI PIU' VICINO (RINT) " + arrotondaRint(arrotonda, cifre));
   }

   // ARROTONDAMENTO PER DIFETTO
   public static double arrotondaPerDifetto (double value, int numCifreDecimali) {
      double temp = Math.pow(10, numCifreDecimali);
      return Math.floor(value * temp) / temp;
   }

   // ARROTONDAMENTO PER ECCESSO
   public static double arrotondaPerEccesso(double value, int numCifreDecimali) {
      double temp = Math.pow(10, numCifreDecimali);
      return Math.ceil(value * temp) / temp;
   }

   // ARROTONDAMENTO CLASSICO
   public static double arrotonda(double value, int numCifreDecimali) {
      double temp = Math.pow(10, numCifreDecimali);
      return Math.round(value * temp) / temp;
   }

   // ARROTONDAMENTO AL NUMERO PARI PIU' VICINO (SE EQUIDISTANTE)
   public static double arrotondaRint(double value, int numCifreDecimali) {
      double temp = Math.pow(10, numCifreDecimali);
      return Math.rint(value * temp) / temp;
   }
}

Inserendo nel JOptionPane il valore 2.5 e 0 cifre decimali, l'output sarà il seguente:

2.5 PER DIFETTO (FLOOR) 2.0
2.5 PER ECCESSO (CEIL) 3.0
2.5 PER CLASSICO (ROUND) 3.0
2.5 AL PARI PIU' VICINO (RINT) 2.0

Notiamo il RINT che restituisce il numero pari più vicino in caso di equidistanza del valore decimale.

Facciamo la controprova inserendo nel JOptionPane il valore 3.5 e 0 cifre decimali, l'output sarà il seguente: 

3.5 PER DIFETTO (FLOOR) 3.0
3.5 PER ECCESSO (CEIL) 4.0
3.5 PER CLASSICO (ROUND) 4.0
3.5 AL PARI PIU' VICINO (RINT) 4.0

Aumentiamo ora le cifre decimali a nostra disposizione inserendo 4.67774 e 2 cifre decimali:

4.67774 PER DIFETTO (FLOOR) 4.67
4.67774 PER ECCESSO (CEIL) 4.68
4.67774 PER CLASSICO (ROUND) 4.68
4.67774 AL PARI PIU' VICINO (RINT) 4.68


Infine possiamo limitarci alla sola rappresentazione arrotondata sfruttando NumberFormat, DecimalFormat della libreria java.text o, da Java 5 in poi, la rappresentazione formattata di System.out.

Guardiamo l'esempio con arrotondamento alla terza cifra decimale dopo la virgola:

package arrotondamenti;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import javax.swing.JOptionPane;

public class TestRappresentazioneArrotondamenti {
   public static void main(String[] args) {
      double arrotonda = Double.parseDouble(JOptionPane.showInputDialog("Inserisci il numero da arrotondare"));

      System.out.println("Arrotondamento di " +arrotonda);

     // NumberFormat (di default arrotondamento in stile Math.rint())
     NumberFormat numForm = NumberFormat.getInstance();
     numForm.setMinimumFractionDigits(3);
     numForm.setMaximumFractionDigits(3);
     numForm.setRoundingMode(RoundingMode.CEILING); // solo da JAVA 6 in poi
     System.out.println(numForm.format(arrotonda));

     // DecimalFormat (di default arrotondamento in stile Math.rint())
     DecimalFormat decForm = new DecimalFormat("#.###", new DecimalFormatSymbols());
     decForm.setRoundingMode(RoundingMode.CEILING); // solo da JAVA 6 in poi
     System.out.println(decForm.format(arrotonda));

      // da Java 5 (arrotondamento in stile Math.round())
      System.out.format("%.3f", arrotonda);
      System.out.println();
   }
}

Proviamo un possibile output inserendo il valore 20000.125555:

Arrotondamento di 20000.125555
20.000,126
20000,126
20000,126

Alcune note:
- NumberFormat inserisce anche i separatori di migliaia, milioni, ecc..., ecc...
- Di default NumberFormat e DecimalFormat usano l'arrotondamento come Math.rint() e può essere modificato solo da Java 6 in poi utilizzando setRoundingMode() che prende come parametro un RoundingMode (è una Enum Java con i vari modi di arrotondamento)
- System.out.format usa invece l'arrotondamento in stile Math.round() e non può essere modificato


Commenti

Post popolari in questo blog

Eclipse: Shortcuts (scorciatoie) da tastiera

Strutture dati: List, Set, Map

Creare un eseguibile Java