BigDecimal et BigInteger en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons démontrer BigDecimal et les classes BigInteger .

Nous allons décrire les deux types de données, leurs caractéristiques et leurs scénarios d'utilisation. Nous couvrirons également brièvement les différentes opérations utilisant les deux classes.

2. BigDecimal

BigDecimal représente un nombre décimal signé de précision arbitraire immuable . Il se compose de deux parties:

  • Valeur non mise à l'échelle - un entier de précision arbitraire
  • Echelle - un entier 32 bits représentant le nombre de chiffres à droite de la virgule décimale

Par exemple, le BigDecimal 3.14 a la valeur non mise à l'échelle de 314 et l'échelle de 2.

Nous utilisons BigDecimal pour l'arithmétique de haute précision. Nous l'utilisons également pour les calculs nécessitant une maîtrise de l'échelle et un arrondissement du comportement . Les calculs impliquant des transactions financières en sont un exemple.

Nous pouvons créer un objet BigDecimal à partir de String , tableau de caractères, int , long et BigInteger :

@Test public void whenBigDecimalCreated_thenValueMatches() { BigDecimal bdFromString = new BigDecimal("0.1"); BigDecimal bdFromCharArray = new BigDecimal(new char[] {'3','.','1','6','1','5'}); BigDecimal bdlFromInt = new BigDecimal(42); BigDecimal bdFromLong = new BigDecimal(123412345678901L); BigInteger bigInteger = BigInteger.probablePrime(100, new Random()); BigDecimal bdFromBigInteger = new BigDecimal(bigInteger); assertEquals("0.1",bdFromString.toString()); assertEquals("3.1615",bdFromCharArray.toString()); assertEquals("42",bdlFromInt.toString()); assertEquals("123412345678901",bdFromLong.toString()); assertEquals(bigInteger.toString(),bdFromBigInteger.toString()); }

Nous pouvons également créer BigDecimal à partir du double :

@Test public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() { BigDecimal bdFromDouble = new BigDecimal(0.1d); assertNotEquals("0.1", bdFromDouble.toString()); }

Cependant, le résultat, dans ce cas, est différent de l'attendu (c'est-à-dire 0,1). Ceci est dû au fait:

  • le double constructeur fait une traduction exacte
  • 0.1 n'a pas de représentation exacte en double

Par conséquent, nous devrions utiliser le constructeur S tring au lieu du double constructeur .

De plus, nous pouvons convertir double et long en BigInteger en utilisant la méthode statique valueOf :

@Test public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() { BigDecimal bdFromLong1 = BigDecimal.valueOf(123412345678901L); BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2); BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d); assertEquals("123412345678901", bdFromLong1.toString()); assertEquals("1234123456789.01", bdFromLong2.toString()); assertEquals("0.1", bdFromDouble.toString()); }

Cette méthode convertit le double en sa représentation String avant la conversion en BigDecimal . De plus, il peut réutiliser des instances d'objet.

Par conséquent, nous devrions utiliser la méthode valueOf de préférence aux constructeurs .

3. Opérations sur BigDecimal

Tout comme les autres classes Number ( Integer , Long , Double etc.), BigDecimal fournit des opérations pour les opérations arithmétiques et de comparaison. Il fournit également des opérations de manipulation d'échelle, d'arrondi et de conversion de format.

Il ne surcharge pas les opérateurs arithmétiques (+, -, /, *) ou logiques (>. <Etc). Au lieu de cela, nous utilisons les méthodes correspondantes - ajouter , soustraire , multiplier , diviser et comparer à.

BigDecimal dispose de méthodes pour extraire divers attributs, tels que la précision, l'échelle et le signe :

@Test public void whenGettingAttributes_thenExpectedResult() { BigDecimal bd = new BigDecimal("-12345.6789"); assertEquals(9, bd.precision()); assertEquals(4, bd.scale()); assertEquals(-1, bd.signum()); }

Nous comparons la valeur de deux BigDecimals à l'aide de la méthode compareTo :

@Test public void whenComparingBigDecimals_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); BigDecimal bd3 = new BigDecimal("2.0"); assertTrue(bd1.compareTo(bd3)  0); assertTrue(bd1.compareTo(bd2) == 0); assertTrue(bd1.compareTo(bd3) = 0); assertTrue(bd1.compareTo(bd3) != 0); }

Cette méthode ignore l'échelle lors de la comparaison.

D'autre part, la méthode equals considère deux objets BigDecimal comme égaux uniquement s'ils sont égaux en valeur et en échelle . Ainsi, BigDecimals 1.0 et 1.00 ne sont pas égaux lorsqu'ils sont comparés par cette méthode.

@Test public void whenEqualsCalled_thenSizeAndScaleMatched() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); assertFalse(bd1.equals(bd2)); }

Nous effectuons des opérations arithmétiques en appelant les méthodes correspondantes :

@Test public void whenPerformingArithmetic_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("4.0"); BigDecimal bd2 = new BigDecimal("2.0"); BigDecimal sum = bd1.add(bd2); BigDecimal difference = bd1.subtract(bd2); BigDecimal quotient = bd1.divide(bd2); BigDecimal product = bd1.multiply(bd2); assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0); assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0); assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0); assertTrue(product.compareTo(new BigDecimal("8.0")) == 0); }

Étant donné que BigDecimal est immuable, ces opérations ne modifient pas les objets existants. Au contraire, ils renvoient de nouveaux objets.

4. Arrondi et BigDecimal

En arrondissant un nombre, nous le remplaçons par un autre ayant une représentation plus courte, plus simple et plus significative . Par exemple, nous arrondissons 24,784917 $ à 24,78 $ car nous n'avons pas de centimes fractionnaires.

La précision et le mode d'arrondi à utiliser varient en fonction du calcul. Par exemple, les déclarations de revenus fédérales américaines spécifient d'arrondir à des montants en dollars entiers en utilisant HALF_UP .

Il existe deux classes qui contrôlent le comportement d'arrondi: RoundingMode et MathContext .

Le RoundingMode enum offre huit modes d' arrondissement:

  • PLAFOND - arrondit vers l'infini positif
  • FLOOR - arrondit vers l'infini négatif
  • UP - arrondit à zéro
  • DOWN - arrondit vers zéro
  • HALF_UP - arrondit vers le «plus proche voisin» sauf si les deux voisins sont équidistants, auquel cas arrondit vers le haut
  • HALF_DOWN - arrondit vers le «plus proche voisin» sauf si les deux voisins sont équidistants, auquel cas arrondit vers le bas
  • HALF_EVEN - arrondit vers le «voisin le plus proche» sauf si les deux voisins sont équidistants, auquel cas, arrondit vers le voisin pair
  • NON NÉCESSAIRE - aucun arrondi n'est nécessaire et ArithmeticException est levée si aucun résultat exact n'est possible

Le mode d'arrondi HALF_EVEN minimise le biais dû aux opérations d'arrondi. Il est fréquemment utilisé. Il est également connu sous le nom d'arrondi du banquier .

MathContext encapsule à la fois le mode de précision et d'arrondi . Il existe quelques MathContexts prédéfinis:

  • DECIMAL32 - Précision de 7 chiffres et mode d'arrondi de HALF_EVEN
  • DECIMAL64 - Précision de 16 chiffres et mode d'arrondi de HALF_EVEN
  • DECIMAL128 - Précision de 34 chiffres et mode d'arrondi de HALF_EVEN
  • UNLIMITED - arithmétique de précision illimitée

En utilisant cette classe, nous pouvons arrondir un nombre BigDecimal en utilisant la précision et le comportement d'arrondi spécifiés:

@Test public void whenRoundingDecimal_thenExpectedResult() { BigDecimal bd = new BigDecimal("2.5"); // Round to 1 digit using HALF_EVEN BigDecimal rounded = bd .round(new MathContext(1, RoundingMode.HALF_EVEN)); assertEquals("2", rounded.toString()); }

Examinons maintenant le concept d'arrondi à l'aide d'un exemple de calcul.

Écrivons une méthode pour calculer le montant total à payer pour un article en fonction d'une quantité et d'un prix unitaire. Appliquons également un taux d'actualisation et un taux de taxe de vente. Nous arrondissons le résultat final à cents en utilisant la méthode setScale :

public static BigDecimal calculateTotalAmount(BigDecimal quantity, BigDecimal unitPrice, BigDecimal discountRate, BigDecimal taxRate) { BigDecimal amount = quantity.multiply(unitPrice); BigDecimal discount = amount.multiply(discountRate); BigDecimal discountedAmount = amount.subtract(discount); BigDecimal tax = discountedAmount.multiply(taxRate); BigDecimal total = discountedAmount.add(tax); // round to 2 decimal places using HALF_EVEN BigDecimal roundedTotal = total.setScale(2, RoundingMode.HALF_EVEN); return roundedTotal; }

Maintenant, écrivons un test unitaire pour cette méthode:

@Test public void givenPurchaseTxn_whenCalculatingTotalAmount_thenExpectedResult() { BigDecimal quantity = new BigDecimal("4.5"); BigDecimal unitPrice = new BigDecimal("2.69"); BigDecimal discountRate = new BigDecimal("0.10"); BigDecimal taxRate = new BigDecimal("0.0725"); BigDecimal amountToBePaid = BigDecimalDemo .calculateTotalAmount(quantity, unitPrice, discountRate, taxRate); assertEquals("11.68", amountToBePaid.toString()); }

5. BigInteger

BigInteger représente des entiers de précision arbitraire immuables . Il est similaire aux types entiers primitifs mais autorise des valeurs arbitraires élevées.

Il est utilisé lorsque les entiers impliqués sont plus grands que la limite du type long . Par exemple, le factoriel de 50 est 30414093201713378043612608166064768844377641568960512000000000000. Cette valeur est trop grande pour un type de données int ou long à gérer. Il ne peut être stocké que dans une variable BigInteger .

Il est largement utilisé dans les applications de sécurité et de cryptographie.

Nous pouvons créer BigInteger à partir d'un tableau d' octets ou d'une chaîne :

@Test public void whenBigIntegerCreatedFromConstructor_thenExpectedResult() { BigInteger biFromString = new BigInteger("1234567890987654321"); BigInteger biFromByteArray = new BigInteger( new byte[] { 64, 64, 64, 64, 64, 64 }); BigInteger biFromSignMagnitude = new BigInteger(-1, new byte[] { 64, 64, 64, 64, 64, 64 }); assertEquals("1234567890987654321", biFromString.toString()); assertEquals("70644700037184", biFromByteArray.toString()); assertEquals("-70644700037184", biFromSignMagnitude.toString()); }

De plus, nous pouvons convertir un long en BigInteger en utilisant la méthode statique valueOf :

@Test public void whenLongConvertedToBigInteger_thenValueMatches() { BigInteger bi = BigInteger.valueOf(2305843009213693951L); assertEquals("2305843009213693951", bi.toString()); }

6. Opérations sur BigInteger

Semblable à int et long , BigInteger implémente toutes les opérations arithmétiques et logiques. Mais, cela ne surcharge pas les opérateurs.

Il implémente également les méthodes correspondantes de la classe Math : abs , min , max , pow , signum .

Nous comparons la valeur de deux BigIntegers en utilisant la méthode compareTo :

@Test public void givenBigIntegers_whentCompared_thenExpectedResult() { BigInteger i = new BigInteger("123456789012345678901234567890"); BigInteger j = new BigInteger("123456789012345678901234567891"); BigInteger k = new BigInteger("123456789012345678901234567892"); assertTrue(i.compareTo(i) == 0); assertTrue(j.compareTo(i) > 0); assertTrue(j.compareTo(k) < 0); }

Nous effectuons des opérations arithmétiques en appelant les méthodes correspondantes:

@Test public void givenBigIntegers_whenPerformingArithmetic_thenExpectedResult() { BigInteger i = new BigInteger("4"); BigInteger j = new BigInteger("2"); BigInteger sum = i.add(j); BigInteger difference = i.subtract(j); BigInteger quotient = i.divide(j); BigInteger product = i.multiply(j); assertEquals(new BigInteger("6"), sum); assertEquals(new BigInteger("2"), difference); assertEquals(new BigInteger("2"), quotient); assertEquals(new BigInteger("8"), product); }

Comme BigInteger est immuable, ces opérations ne modifient pas les objets existants. Contrairement à, int et long , ces opérations ne débordent pas.

BigInteger a les opérations sur les bits similaires à int et long . Mais, nous devons utiliser les méthodes au lieu des opérateurs:

@Test public void givenBigIntegers_whenPerformingBitOperations_thenExpectedResult() { BigInteger i = new BigInteger("17"); BigInteger j = new BigInteger("7"); BigInteger and = i.and(j); BigInteger or = i.or(j); BigInteger not = j.not(); BigInteger xor = i.xor(j); BigInteger andNot = i.andNot(j); BigInteger shiftLeft = i.shiftLeft(1); BigInteger shiftRight = i.shiftRight(1); assertEquals(new BigInteger("1"), and); assertEquals(new BigInteger("23"), or); assertEquals(new BigInteger("-8"), not); assertEquals(new BigInteger("22"), xor); assertEquals(new BigInteger("16"), andNot); assertEquals(new BigInteger("34"), shiftLeft); assertEquals(new BigInteger("8"), shiftRight); }

Il a des méthodes de manipulation de bits supplémentaires :

@Test public void givenBigIntegers_whenPerformingBitManipulations_thenExpectedResult() { BigInteger i = new BigInteger("1018"); int bitCount = i.bitCount(); int bitLength = i.bitLength(); int getLowestSetBit = i.getLowestSetBit(); boolean testBit3 = i.testBit(3); BigInteger setBit12 = i.setBit(12); BigInteger flipBit0 = i.flipBit(0); BigInteger clearBit3 = i.clearBit(3); assertEquals(8, bitCount); assertEquals(10, bitLength); assertEquals(1, getLowestSetBit); assertEquals(true, testBit3); assertEquals(new BigInteger("5114"), setBit12); assertEquals(new BigInteger("1019"), flipBit0); assertEquals(new BigInteger("1010"), clearBit3); }

BigInteger fournit des méthodes pour le calcul GCD et l'arithmétique modulaire :

@Test public void givenBigIntegers_whenModularCalculation_thenExpectedResult() { BigInteger i = new BigInteger("31"); BigInteger j = new BigInteger("24"); BigInteger k = new BigInteger("16"); BigInteger gcd = j.gcd(k); BigInteger multiplyAndmod = j.multiply(k).mod(i); BigInteger modInverse = j.modInverse(i); BigInteger modPow = j.modPow(k, i); assertEquals(new BigInteger("8"), gcd); assertEquals(new BigInteger("12"), multiplyAndmod); assertEquals(new BigInteger("22"), modInverse); assertEquals(new BigInteger("7"), modPow); }

Il a également des méthodes liées à la génération principale et aux tests de primalité :

@Test public void givenBigIntegers_whenPrimeOperations_thenExpectedResult() { BigInteger i = BigInteger.probablePrime(100, new Random()); boolean isProbablePrime = i.isProbablePrime(1000); assertEquals(true, isProbablePrime); }

7. Conclusion

Dans ce tutoriel rapide, nous avons exploré les classes BigDecimal et BigInteger. Ils sont utiles pour les calculs numériques avancés où les types entiers primitifs ne suffisent pas.

Comme d'habitude, le code source complet peut être trouvé sur GitHub.