En Java, lorsque vous manipulez des nombres décimaux (comme des montants financiers, des taux d’intérêt, ou des calculs précis), vous avez le choix entre plusieurs types :
float
Float
double
Double
BigDecimal
Problème : float et double ne sont pas adaptés pour les calculs financiers en raison de leur imprécision due à la représentation binaire des nombres décimaux.
Solution : BigDecimal est conçu pour les calculs où la précision est critique (comme en banque, comptabilité, ou sciences).
Les types float et double utilisent une représentation binaire (norme IEEE 754), ce qui entraîne des erreurs d’arrondi pour les nombres décimaux non représentables exactement en binaire.
Exemple 1 : Addition simple
public class FloatDoubleProblem { public static void main(String[] args) { double a = 0.1; double b = 0.2; double sum = a + b; System.out.println(sum); // affiche 0.30000000000000004 (!= 0.3) } }
Erreur d’arrondi due à la représentation binaire.
Exemple 2 : Calcul financier
public class FinancialCalculation { public static void main(String[] args) { double balance = 1000.00; double interestRate = 0.05; // 5% double interest = balance * interestRate; System.out.println(interest); // affiche 50.0 (par chance) double newBalance = balance + interest; System.out.println(newBalance); // affiche 1050.0 (par chance) // mais si on essaye avec des montants plus complexes... double amount = 123.456; double tax = amount * 0.196; // TVA à 19.6% System.out.println(tax); // Cela affiche 24.265176000000002 (!= 24.265176) } } >Donc l'erreur n'est pas acceptable pour un logiciel bancaire ou comptable ! ### b. Problèmes de comparaison >Les erreurs d’arrondi rendent les comparaisons imprévisibles ```java public class ComparisonProblem { public static void main(String[] args) { double a = 0.3; double b = 0.1 + 0.2; System.out.println(a == b); // Affiche false (!) } }
Les valeurs float/double peuvent changer légèrement lors de la sérialisation/désérialisation ou de l’affichage, ce qui est inacceptable pour des montants financiers.
BigDecimal stocke les nombres sous forme de chaînes de caractères (base 10), ce qui élimine les erreurs d’arrondi. Pas de perte de précision pour les calculs décimaux. Comme la plupart du temps nous récupérons des données provenant de fichiers, les valeurs sont souvent des chaînes de caractères comme les données qui viennent d’un Front-end.
Exemple : Addition précise
import java.math.BigDecimal; public class BigDecimalPrecision { public static void main(String[] args) { BigDecimal a = new BigDecimal("0.1"); BigDecimal b = new BigDecimal("0.2"); BigDecimal somme = a.add(b); System.out.println(somme); // affiche 0.3 (précis !) } }
BigDecimal permet de spécifier comment arrondir les résultats (via le RoundingMode) :
RoundingMode
import java.math.BigDecimal; import java.math.RoundingMode; public class BigDecimalRounding { public static void main(String[] args) { BigDecimal value = new BigDecimal("123.456789"); BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // arrondir à 2 décimales System.out.println(rounded); // affichera 123.46 } }
Résultats possibles selon RoundingMode :
BigDecimal est immuable : Chaque opération crée un nouvel objet, ce qui évite les effets de bord. Il est aussi Thread-safe, il peut être utilisé sans risque dans des environnements multi-threads.
BigDecimal est immuable
Thread-safe
d. Précision configurable Vous pouvez définir la précision (nombre de chiffres significatifs) et l’échelle (nombre de décimales) :
import java.math.BigDecimal; import java.math.MathContext; public class BigDecimalPrecisionControl { public static void main(String[] args) { BigDecimal a = new BigDecimal("123.456789"); BigDecimal b = new BigDecimal("987.654321"); // addition avec une précision de 10 chiffres BigDecimal somme = a.add(b, new MathContext(10)); System.out.println(somme); // affiche 1.111111111E+3 (1111.111110) } }
Évitez d’utiliser double ou float pour initialiser un BigDecimal, car cela propage les erreurs d’arrondi :
// Mauvaise pratique (héritage des erreurs de double) BigDecimal bad = new BigDecimal(0.1); // Stocke 0.10000000000000000555... // Bonne pratique BigDecimal good = new BigDecimal("0.1"); // Stocke exactement 0.1
Pour les valeurs simples, utilisez BigDecimal.valueOf() (plus efficace) :
BigDecimal.valueOf()
BigDecimal value = BigDecimal.valueOf(0.1); // Équivalent à new BigDecimal("0.1")`
Pour les montants monétaires, fixez toujours 2 décimales !
import java.math.BigDecimal; import java.math.RoundingMode; public class FinancialAmount { public static void main(String[] args) { BigDecimal amount = new BigDecimal("123.456"); BigDecimal roundedAmount = amount.setScale(2, RoundingMode.HALF_UP); System.out.println(roundedAmount); // Affiche 123.46 } }
Les opérations sur BigDecimal créent de nouveaux objets. Pour des calculs complexes, utilisez des variables intermédiaires :
BigDecimal a = new BigDecimal("100.00"); BigDecimal b = new BigDecimal("20.00"); BigDecimal c = new BigDecimal("5.00"); // peu lisible et inefficace BigDecimal result = a.add(b).subtract(c).multiply(new BigDecimal("1.1")); // meilleure pratique BigDecimal somme = a.add(b); BigDecimal apreReduction = somme.subtract(c); BigDecimal montantFinal = apreReduction.multiply(new BigDecimal("1.1")); // +10%
import java.math.BigDecimal; import java.math.RoundingMode; public class InterestCalculator { public static void main(String[] args) { BigDecimal principal = new BigDecimal("10000.00"); // Capital BigDecimal rate = new BigDecimal("0.05"); // Taux d’intérêt (5%) int years = 5; // Calcul des intérêts composés BigDecimal amount = principal.multiply(BigDecimal.ONE.add(rate).pow(years) ).setScale(2, RoundingMode.HALF_UP); System.out.println("Montant après " + years + " ans: " + amount); // affiche : Montant après 5 ans: 12762.82 } }
import java.math.BigDecimal; import java.math.RoundingMode; public class CurrencyConverter { public static void main(String[] args) { BigDecimal amountEUR = new BigDecimal("100.00"); BigDecimal exchangeRate = new BigDecimal("1.08"); // 1 € = 1.08 $ BigDecimal amountUSD = amountEUR.multiply(exchangeRate) .setScale(2, RoundingMode.HALF_UP); System.out.println("100.00 € = " + amountUSD + " $"); // affiche : 100.00 € = 108.00 USD } }
import java.math.BigDecimal; public class AmountValidator { public static void main(String[] args) { String input = "123.456"; try { BigDecimal amount = new BigDecimal(input); if (amount.compareTo(BigDecimal.ZERO) < 0) { System.out.println("Montant invalide : négatif."); } else if (amount.scale() > 2) { System.out.println("Montant invalide : plus de 2 décimales."); } else { System.out.println("Montant valide : " + amount); } } catch (NumberFormatException e) { System.out.println("Format invalide."); } } }
Les opérations sur BigDecimal sont coûteuses. Pour des boucles, essayez de :
import java.math.BigDecimal; public class PerformanceExample { public static void main(String[] args) { BigDecimal total = BigDecimal.ZERO; for (int i = 0; i < 1000; i++) { BigDecimal amount = new BigDecimal("10.50"); total = total.add(amount); // réutilise 'total' } System.out.println("Total : " + total); } }
Pour les calculs complexes, définissez un MathContext pour limiter la précision intermédiaire
import java.math.BigDecimal; import java.math.MathContext; public class MathContextExample { public static void main(String[] args) { MathContext mc = new MathContext(10); // 10 chiffres de précision BigDecimal a = new BigDecimal("123456789.123456789"); BigDecimal b = new BigDecimal("987654321.987654321"); BigDecimal result = a.divide(b, mc); // division avec précision limitée System.out.println(result); // affiche 1.249999999 (arrondi à 10 chiffres) } }
BigInteger
String
new BigDecimal("0.1")
new BigDecimal(0.1)
setScale(2, RoundingMode.HALF_UP)
BigDecimal.ZERO
BigDecimal.ONE
a.add(b).subtract(c)
MathContext
a.divide(b, new MathContext(10))
Bonnes pratiques :
Exemple final :
import java.math.BigDecimal; import java.math.RoundingMode; public class BankAccount { public static void main(String[] args) { BigDecimal balance = new BigDecimal("1000.00"); BigDecimal interestRate = new BigDecimal("0.05"); BigDecimal interest = balance.multiply(interestRate) .setScale(2, RoundingMode.HALF_UP); BigDecimal newBalance = balance.add(interest); System.out.printf("Solde initial : %s%n", balance); System.out.printf("Intérêts (5%%) : %s%n", interest); System.out.printf("Nouveau solde : %s%n", newBalance); } }
Sortie :
Solde initial : 1000.00 Intérêts (5%) : 50.00 Nouveau solde : 1050.00