BigDecimal и BigInteger в Java

1. Общ преглед

В този урок ще демонстрираме класовете BigDecimal и BigInteger .

Ще опишем двата типа данни, техните характеристики и сценарии за тяхното използване. Също така ще разгледаме накратко различните операции, използвайки двата класа.

2. BigDecimal

BigDecimal представлява неизменяемо десетично число с произволна точност . Състои се от две части:

  • Немащабирана стойност - цяло число с произволна точност
  • Мащаб - 32-битово цяло число, представляващо броя на цифрите вдясно от десетичната запетая

Например BigDecimal 3.14 има мащабираната стойност 314 и скалата 2.

Използваме BigDecimal за високо прецизна аритметика. Използваме го и за изчисления, изискващи контрол върху мащаба и закръгляване на поведението . Такъв пример са изчисленията, включващи финансови транзакции.

Можем да създадем обект BigDecimal от String , масив от символи, int , long и 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()); }

Можем да създадем и BigDecimal от double :

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

В този случай обаче резултатът е различен от очаквания (това е 0,1). Това е така, защото:

  • на двойно строителя прави точен превод
  • 0.1 няма точно представяне в двойно

Следователно трябва да използваме конструктора S tring вместо двойния конструктор .

Освен това можем да преобразуваме double и long в BigInteger, използвайки статичния метод 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()); }

Този метод преобразува двойно в своето представяне String, преди да преобразува в BigDecimal . Освен това може да използва повторно екземпляри на обекти.

Следователно трябва да използваме метода valueOf за предпочитане пред конструкторите .

3. Операции на BigDecimal

Подобно на другите класове с числа ( Integer , Long , Double и т.н.), BigDecimal осигурява операции за аритметични и сравнителни операции. Той също така осигурява операции за манипулиране на мащаба, закръгляване и преобразуване на формат.

Той не претоварва аритметичните (+, -, /, *) или логическите (>. <И т.н.) оператори. Вместо това използваме съответните методи - добавяне , изваждане , умножение , разделяне и сравнение на.

BigDecimal има методи за извличане на различни атрибути, като прецизност, мащаб и знак :

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

Ние сравняваме стойностите на двете BigDecimals използващи 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); }

Този метод игнорира скалата при сравнение.

От друга страна, на за равенство метод счита две BigDecimal обекти като равна, само ако те са равни по стойност и мащаб . По този начин BigDecimals 1.0 и 1.00 не са равни, когато се сравняват по този метод.

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

Извършваме аритметични операции, като извикваме съответните методи :

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

Тъй като BigDecimal е неизменим, тези операции не променят съществуващите обекти. По-скоро връщат нови предмети.

4. Закръгляване и BigDecimal

Закръглявайки число, ние го заместваме с друго, което има по-кратко, по-просто и по-смислено представяне . Например закръгляме 24,784917 $ до 24,78 $, тъй като нямаме дробни центове.

Използваният режим на прецизност и закръгляване варира в зависимост от изчислението. Например, Федералните данъчни декларации на САЩ посочват да се закръглят до цели доларови суми, използвайки HALF_UP .

Има два класа, които контролират поведението закръгляване - RoundingMode и MathContext .

Режимът за закръгляване на преброяването осигурява осем режима на закръгляване:

  • ТАВАН - закръглява към положителна безкрайност
  • ЕТАЖ - закръглява към отрицателна безкрайност
  • НАГОРЕ - закръглява се от нулата
  • НАДОЛУ - закръглява се към нула
  • HALF_UP - закръглява към „най-близкия съсед”, освен ако и двамата съседи са на еднакво разстояние, в този случай закръглява
  • HALF_DOWN - закръглява към „най-близкия съсед“, освен ако и двамата съседи са на еднакво разстояние, в този случай закръглява надолу
  • HALF_EVEN - закръглява към „най-близкия съсед“, освен ако и двамата съседи са на еднакво разстояние, в този случай закръглява към четния съсед
  • НЕОБХОДИМО - не е необходимо закръгляване и се изхвърля ArithmeticException, ако не е възможен точен резултат

Режимът на закръгляване HALF_EVEN минимизира пристрастието поради операциите за закръгляване. Често се използва. Известно е и като закръгляване на банкера .

MathContext капсулира както точност, така и режим на закръгляване . Има няколко предварително дефинирани MathContexts:

  • DECIMAL32 - 7 цифри с точност и режим на закръгляване HALF_EVEN
  • DECIMAL64 - прецизност от 16 цифри и режим на закръгляване HALF_EVEN
  • DECIMAL128 - 34 цифри с точност и режим на закръгляване HALF_EVEN
  • НЕОГРАНИЧЕНО - неограничена прецизна аритметика

Използвайки този клас, можем да закръглим BigDecimal число, използвайки определена точност и поведение на закръгляване:

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

Сега, нека разгледаме концепцията за закръгляване, като използваме примерно изчисление.

Нека напишем метод за изчисляване на общата сума, която трябва да бъде платена за артикул с дадено количество и единична цена. Нека да приложим и дисконтов процент и данък върху продажбите. Закръгляме крайния резултат до центове, като използваме метода 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; }

Сега, нека напишем единичен тест за този метод:

@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 представлява неизменяеми цели числа с произволна точност . Той е подобен на примитивните цели числа, но позволява произволни големи стойности.

Използва се, когато участващите цели числа са по-големи от лимита за дълъг тип. Например факториалът на 50 е 30414093201713378043612608166064768844377641568960512000000000000. Тази стойност е твърде голяма за обработка на тип тип int или long . Може да се съхранява само в променлива BigInteger .

Той се използва широко в приложенията за сигурност и криптография.

Можем да създадем BigInteger от байтов масив или низ :

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

В допълнение, можем да преобразуваме long в BigInteger, като използваме статичния метод valueOf :

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

6. Операции на BigInteger

Подобно на int и long , BigInteger изпълнява всички аритметични и логически операции. Но това не претоварва операторите.

Той също така прилага съответните методи от клас по математика : abs , min , max , pow , signum .

Сравняваме стойността на две BigIntegers, използвайки метода 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); }

Извършваме аритметични операции, като извикваме съответните методи:

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

Тъй като BigInteger е неизменен, тези операции не променят съществуващите обекти. За разлика от int и long , тези операции не преливат.

BigInteger има битови операции, подобни на int и long . Но ние трябва да използваме методите вместо оператори:

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

Той има допълнителни методи за манипулиране на битове :

@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 предоставя методи за GCD изчисление и модулна аритметика :

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

Той също така има методи, свързани с първоначалното поколение и тестването на първичност :

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

7. Заключение

В този бърз урок разгледахме класовете BigDecimal и BigInteger. Те са полезни за разширени числени изчисления, където примитивните цели числа не са достатъчни.

Както обикновено, пълният изходен код може да бъде намерен в GitHub.