BigDecimal și BigInteger în Java

1. Prezentare generală

În acest tutorial, vom demonstra clasele BigDecimal și BigInteger .

Vom descrie cele două tipuri de date, caracteristicile și scenariile lor de utilizare. De asemenea, vom acoperi pe scurt diferitele operațiuni folosind cele două clase.

2. BigDecimal

BigDecimal reprezintă un număr zecimal semnat cu precizie arbitrară imuabilă . Se compune din două părți:

  • Valoare nescalată - un întreg de precizie arbitrar
  • Scală - un număr întreg de 32 de biți reprezentând numărul de cifre din dreapta punctului zecimal

De exemplu, BigDecimal 3.14 are valoarea nescalată de 314 și scara de 2.

Folosim BigDecimal pentru aritmetica de înaltă precizie. De asemenea, îl folosim pentru calcule care necesită control asupra scalei și comportamentul de rotunjire . Un astfel de exemplu sunt calculele care implică tranzacții financiare.

Putem crea un obiect BigDecimal din String , matrice de caractere, int , long și 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()); }

De asemenea, putem crea BigDecimal din dublu :

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

Cu toate acestea, rezultatul, în acest caz, este diferit de cel așteptat (adică 0,1). Asta pentru ca:

  • dublu constructor are o traducere exactă
  • 0.1 nu are o reprezentare exactă în dublu

Prin urmare, ar trebui să folosim constructorul S tring în locul constructorului dublu .

În plus, putem converti dublu și lung în BigInteger folosind metoda statică 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()); }

Această metodă convertește dublu în reprezentarea sa String înainte de a o converti în BigDecimal . În plus, poate reutiliza instanțe de obiect.

Prin urmare, ar trebui să folosim metoda valueOf în preferință pentru constructori .

3. Operațiuni pe BigDecimal

La fel ca celelalte clase numerice ( întreg , lung , dublu etc.), BigDecimal oferă operații pentru operații de aritmetică și comparație. De asemenea, oferă operații pentru manipularea scalei, rotunjirea și conversia formatului.

Nu supraîncarcă operatorii aritmetici (+, -, /, *) sau logici (>. <Etc). În schimb, vom folosi metodele corespunzătoare - adăugare , scădere , se multiplică , diviza și compareTo.

BigDecimal are metode pentru a extrage diverse atribute, precum precizie, scară și semn :

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

Comparăm valoarea a două BigDecimals folosind metoda 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); }

Această metodă ignoră scara în timp ce compară.

Pe de altă parte, este egal metoda consideră două BigDecimal obiecte ca fiind egale numai dacă acestea sunt egale în valoare și la scară . Astfel, BigDecimals 1.0 și 1.00 nu sunt egale în comparație cu această metodă.

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

Efectuăm operații aritmetice apelând metodele corespunzătoare :

@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); }

Deoarece BigDecimal este imuabil, aceste operații nu modifică obiectele existente. Mai degrabă, returnează obiecte noi.

4. Rotunjire și BigDecimal

Prin rotunjirea unui număr, îl înlocuim cu un alt care are o reprezentare mai scurtă, mai simplă și mai semnificativă . De exemplu, rotunjim 24,784917 USD la 24,78 USD, deoarece nu avem cenți fracționali.

Modul de precizie și rotunjire care trebuie utilizat variază în funcție de calcul. De exemplu, declarațiile fiscale federale din SUA specifică rotunjirea la sume întregi de dolari folosind HALF_UP .

Există două clase care controlează comportamentul de rotunjire - RoundingMode și MathContext .

RoundingMode enum oferă opt moduri de rotunjire:

  • PLAFON - rotunjeste spre infinit pozitiv
  • ETAJ - rotunjeste spre infinit negativ
  • UP - se rotunde de la zero
  • JOS - rotunjeste spre zero
  • HALF_UP - rotunde către „cel mai apropiat vecin”, cu excepția cazului în care ambii vecini sunt echidistanți, caz în care se rotunjește
  • HALF_DOWN - rotunde către „cel mai apropiat vecin”, cu excepția cazului în care ambii vecini sunt echidistanți, caz în care se rotunjește
  • HALF_EVEN - rotunde către „cel mai apropiat vecin”, cu excepția cazului în care ambii vecini sunt echidistanți, caz în care se rotesc către vecinul egal
  • INUTIL - nu este necesară rotunjirea și ArithmeticException este aruncat dacă nu este posibil un rezultat exact

Modul de rotunjire HALF_EVEN minimizează prejudecățile datorate operațiilor de rotunjire. Este frecvent utilizat. Este, de asemenea, cunoscut sub numele de rotunjire a bancherului .

MathContext încapsulează atât modul de precizie, cât și modul de rotunjire . Există câteva MathContexte predefinite:

  • DECIMAL32 - precizie de 7 cifre și un mod de rotunjire de HALF_EVEN
  • DECIMAL64 - precizie de 16 cifre și un mod de rotunjire de HALF_EVEN
  • DECIMAL128 - precizie de 34 de cifre și un mod de rotunjire de HALF_EVEN
  • NELIMITAT - aritmetică de precizie nelimitată

Folosind această clasă, putem rotunji un număr BigDecimal folosind precizia specificată și comportamentul de rotunjire:

@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()); }

Acum, să examinăm conceptul de rotunjire folosind un eșantion de calcul.

Să scriem o metodă pentru a calcula suma totală care trebuie plătită pentru un articol având o cantitate și un preț unitar. Să aplicăm, de asemenea, o rată de reducere și o rată de impozitare pe vânzări. Rotunjim rezultatul final la cenți folosind metoda 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; }

Acum, să scriem un test unitar pentru această metodă:

@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 reprezintă numere întregi imuabile de precizie arbitrară . Este similar cu tipurile întregi primitive, dar permite valori mari arbitrare.

Se folosește atunci când numerele întregi implicate sunt mai mari decât limita de tip lung . De exemplu, factorialul de 50 este 3041409320171337804361260816606476884437764156896051200000000000000. Această valoare este prea mare pentru un tip de date int sau lung pentru a fi tratat. Poate fi stocat doar într-o variabilă BigInteger .

Este utilizat pe scară largă în aplicații de securitate și criptografie.

Putem crea BigInteger dintr-o matrice de octeți sau șir :

@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()); }

În plus, putem converti un lung în BigInteger folosind metoda statică valueOf :

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

6. Operațiuni pe BigInteger

Similar cu int și long , BigInteger implementează toate operațiile aritmetice și logice. Dar nu supraîncarcă operatorii.

De asemenea, implementează metodele corespunzătoare din clasa Math : abs , min , max , pow , signum .

Comparăm valoarea a două BigIntegers folosind metoda 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); }

Efectuăm operații aritmetice apelând metodele corespunzătoare:

@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); }

Deoarece BigInteger este imuabil, aceste operații nu modifică obiectele existente. Spre deosebire de, int și long , aceste operațiuni nu se revarsă.

BigInteger are operații bit similare cu int și long . Dar, trebuie să folosim metodele în locul operatorilor:

@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); }

Are metode suplimentare de manipulare a biților :

@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 oferă metode pentru calculul GCD și aritmetica modulară :

@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); }

De asemenea, are metode legate de testarea primară și de primă generație :

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

7. Concluzie

În acest tutorial rapid, am explorat clasele BigDecimal și BigInteger. Acestea sunt utile pentru calcule numerice avansate în care tipurile întregi primitive nu sunt suficiente.

Ca de obicei, codul sursă complet poate fi găsit pe GitHub.