BigDecimal e BigInteger in Java

1. Panoramica

In questo tutorial, mostreremo le classi BigDecimal e BigInteger .

Descriveremo i due tipi di dati, le loro caratteristiche e i loro scenari di utilizzo. Tratteremo anche brevemente le varie operazioni usando le due classi.

2. BigDecimal

BigDecimal rappresenta un numero decimale con segno di precisione arbitraria immutabile . Consiste di due parti:

  • Valore non graduato: un numero intero di precisione arbitraria
  • Scala: un numero intero a 32 bit che rappresenta il numero di cifre a destra del separatore decimale

Ad esempio, il BigDecimal 3.14 ha il valore non graduato di 314 e la scala di 2.

Usiamo BigDecimal per aritmetica ad alta precisione. Lo usiamo anche per i calcoli che richiedono il controllo sulla scala e l'arrotondamento del comportamento . Uno di questi esempi sono i calcoli che coinvolgono transazioni finanziarie.

Possiamo creare un oggetto BigDecimal da String , array di caratteri, int , long e 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()); }

Possiamo anche creare BigDecimal da double :

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

Tuttavia, il risultato, in questo caso, è diverso da quello previsto (ovvero 0,1). Questo è perché:

  • il doppio costruttore esegue una traduzione esatta
  • 0.1 non ha una rappresentazione esatta in double

Pertanto, dovremmo usare il costruttore S tring invece del doppio costruttore .

Inoltre, possiamo convertire double e long in BigInteger utilizzando il metodo statico 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()); }

Questo metodo converte double nella sua rappresentazione String prima della conversione in BigDecimal . Inoltre, può riutilizzare le istanze degli oggetti.

Quindi, dovremmo usare il metodo valueOf rispetto ai costruttori .

3. Operazioni su BigDecimal

Proprio come le altre classi Number ( Integer , Long , Double ecc.), BigDecimal fornisce operazioni per operazioni aritmetiche e di confronto. Fornisce inoltre operazioni per la manipolazione della scala, l'arrotondamento e la conversione del formato.

Non sovraccarica gli operatori aritmetici (+, -, /, *) o logici (>. <Ecc). Invece, usiamo i metodi corrispondenti: addizione , sottrazione , moltiplicazione , divisione e confronto.

BigDecimal ha metodi per estrarre vari attributi, come precisione, scala e segno :

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

Confrontiamo il valore di due BigDecimals utilizzando il metodo 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); }

Questo metodo ignora la scala durante il confronto.

D'altra parte, il metodo equals considera due oggetti BigDecimal uguali solo se sono uguali in valore e scala . Pertanto, BigDecimals 1.0 e 1.00 non sono uguali se confrontati con questo metodo.

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

Eseguiamo operazioni aritmetiche chiamando i metodi corrispondenti :

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

Poiché BigDecimal è immutabile, queste operazioni non modificano gli oggetti esistenti. Piuttosto, restituiscono nuovi oggetti.

4. Arrotondamento e BigDecimal

Arrotondando un numero, lo sostituiamo con un altro avente una rappresentazione più breve, più semplice e più significativa . Ad esempio, arrotondiamo $ 24,784917 a $ 24,78 poiché non abbiamo centesimi frazionari.

La modalità di precisione e arrotondamento da utilizzare varia a seconda del calcolo. Ad esempio, le dichiarazioni dei redditi federali statunitensi specificano di arrotondare gli importi in dollari interi utilizzando HALF_UP .

Ci sono due classi che controllano il comportamento di arrotondamento - RoundingMode e MathContext .

L' enum RoundingMode fornisce otto modalità di arrotondamento:

  • SOFFITTO - arrotonda verso l'infinito positivo
  • FLOOR - arrotonda verso l'infinito negativo
  • UP - arrotonda lontano da zero
  • GIÙ - arrotonda verso lo zero
  • HALF_UP - arrotonda verso il "vicino più prossimo" a meno che entrambi i vicini non siano equidistanti, nel qual caso arrotonda per eccesso
  • HALF_DOWN - arrotonda verso il "vicino più prossimo" a meno che entrambi i vicini non siano equidistanti, nel qual caso arrotonda per difetto
  • HALF_EVEN - arrotonda verso il "vicino più vicino" a meno che entrambi i vicini non siano equidistanti, nel qual caso, arrotonda verso il vicino pari
  • NON NECESSARIO: non è necessario alcun arrotondamento e viene generata ArithmeticException se non è possibile un risultato esatto

La modalità di arrotondamento HALF_EVEN riduce al minimo la distorsione dovuta alle operazioni di arrotondamento. È usato frequentemente. È anche noto come arrotondamento del banchiere .

MathContext incapsula sia la modalità di precisione che quella di arrotondamento . Esistono pochi MathContext predefiniti:

  • DECIMAL32 : precisione di 7 cifre e modalità di arrotondamento HALF_EVEN
  • DECIMAL64 - Precisione di 16 cifre e modalità di arrotondamento HALF_EVEN
  • DECIMAL128 - 34 cifre di precisione e una modalità di arrotondamento di HALF_EVEN
  • ILLIMITATO - aritmetica di precisione illimitata

Usando questa classe, possiamo arrotondare un numero BigDecimal usando la precisione e il comportamento di arrotondamento specificati:

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

Esaminiamo ora il concetto di arrotondamento utilizzando un calcolo di esempio.

Let's write a method to calculate the total amount to be paid for an item given a quantity and unit price. Let's also apply a discount rate and sales tax rate. We round the final result to cents by using the setScale method:

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

Now, let's write a unit test for this method:

@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 represents immutable arbitrary-precision integers. It is similar to the primitive integer types but allows arbitrary large values.

It is used when integers involved are larger than the limit of long type. For example, the factorial of 50 is 30414093201713378043612608166064768844377641568960512000000000000. This value is too big for an int or long data type to handle. It can only be stored in a BigInteger variable.

It is widely used in security and cryptography applications.

We can create BigInteger from a byte array or String:

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

In addition, we can convert a long to BigInteger using the static method valueOf:

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

6. Operations on BigInteger

Similar to int and long, BigInteger implements all the arithmetic and logical operations. But, it does not overload the operators.

It also implements the corresponding methods from Math class: abs, min, max, pow, signum.

We compare the value of two BigIntegers using the compareTo method:

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

We perform arithmetic operations by calling the corresponding methods:

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

As BigInteger is immutable, these operations do not modify the existing objects. Unlike, int and long, these operations do not overflow.

BigInteger ha le operazioni sui bit simili a int e long . Ma dobbiamo usare i metodi invece degli operatori:

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

Ha metodi di manipolazione dei bit aggiuntivi :

@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 fornisce metodi per il calcolo GCD e l'aritmetica modulare :

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

Ha anche metodi relativi alla prima generazione e al test di primalità :

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

7. Conclusione

In questo breve tutorial, abbiamo esplorato le classi BigDecimal e BigInteger. Sono utili per calcoli numerici avanzati in cui i tipi interi primitivi non sono sufficienti.

Come al solito, il codice sorgente completo può essere trovato su GitHub.