mardi 8 mai 2018

BigDecimal : pour les calculs avec une grande précision

La précision du calcul avec les nombres en virgule flottante dépend fortement de la taille (nombre de bits) de ces nombres.
Ainsi, il est très facile de montrer les limites des différents types élémentaires largement utilisés. Par exemple, le code suivant qui utilise le type float :

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemple = (float)34/145;
  System.out.println(exemple);
  
 }
}

Affiche le résultat suivant :


Pour augmenter la précision, il est possible d'utiliser le type double. Le code suivant :

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  double exemple = (double)34/145;
  System.out.println(exemple);
  
 }
}

Donne le résultat suivant :


Les deux résultats sont loin d'être complets et précis. Pour atteindre une précision beaucoup plus supérieure, nous pouvons utiliser les BigDecimal. BigDecimal est une classe du package java.math. Elle permet de créer des nombres avec des grandes précisions et de faire des opérations arithmétiques sur ces nombres. Comme étant une classe, les opérateurs traditionnels (+, -, *, /, %) ne peuvent pas être appliqués directement. Il faut faire recours à des fonctions pour effectuer ces opérations.
Le code suivant fait le même calcul que les deux codes cités ci-dessus. La précision choisie est 20 chiffres après la virgule :

import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result = bd34.divide(bd145, 20, RoundingMode.DOWN);
  System.out.println(result.toPlainString());
  
 }
}

Il affiche le résultat suivant :


Mettons les trois codes ensemble et augmontons la précision à 100 chiffres après la virgule :

import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemplef = (float)34/145;
  System.out.println("Float      : " + exemplef);
  
  double exempled = (double)34/145;
  System.out.println("Double     : " + exempled);
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result = bd34.divide(bd145, 100, RoundingMode.DOWN);
  System.out.println("BigDecimal : " +result.toPlainString());
  
 }
}

Le résultat sera :


Il est important de noter que ces opérations sont beaucoup plus lentes. Un test rapide :


import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemplef;
  long d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   exemplef = (float)34/145;
  long f = System.currentTimeMillis(); 
  System.out.println("Float temps 1000 opérations    : " + (f - d));
  
  double exempled;
  d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   exempled = (double)34/145;
  f = System.currentTimeMillis(); 
  System.out.println("Double temps 1000 opérations    : " + (f - d));
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result;
  d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   result = bd34.divide(bd145, 100, RoundingMode.DOWN);
  f = System.currentTimeMillis(); 
  System.out.println("BigDecimal temps 1000 opérations : " + (f - d));
  
 }
}

Le résultat est :

Nous voyons clairement que 1000 opérations de division en BigDecimal sont beaucoup plus lentes que les opérateurs traditionnels.