Перейти к основному содержимому

BigDecimal и BigInteger в Java

· 9 мин. чтения

1. Обзор

В этом уроке мы продемонстрируем классы BigDecimal и BigInteger .

Мы опишем два типа данных, их характеристики и сценарии их использования. Мы также кратко рассмотрим различные операции с использованием этих двух классов.

2. Большой десятичный

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 String вместо конструктора double .

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

Этот метод преобразует double в представление String перед преобразованием в BigDecimal . Кроме того, он может повторно использовать экземпляры объектов.

Следовательно, мы должны использовать метод valueOf вместо конструкторов .

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

Как и другие классы Number ( 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(bd3.compareTo(bd1) > 0);
assertTrue(bd1.compareTo(bd2) == 0);
assertTrue(bd1.compareTo(bd3) <= 0);
assertTrue(bd1.compareTo(bd2) >= 0);
assertTrue(bd1.compareTo(bd3) != 0);
}

Этот метод игнорирует масштаб при сравнении.

С другой стороны, метод equals считает два объекта 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 .

Перечисление RoundingMode обеспечивает восемь режимов округления :

  • CEILING – округление до положительной бесконечности
  • ПОЛ – округление до минус бесконечности
  • ВВЕРХ – округление от нуля
  • ВНИЗ – округление до нуля
  • HALF_UP — округление к «ближайшему соседу», если только оба соседа не равноудалены, и в этом случае округление вверх
  • HALF_DOWN — округление к «ближайшему соседу», если только оба соседа не равноудалены, и в этом случае округление вниз
  • HALF_EVEN — округление к «ближайшему соседу», если только оба соседа не равноудалены, и в этом случае округление к четному соседу
  • НЕОБХОДИМО – округление не требуется, и если точный результат невозможен, выдается исключение ArithmeticException .

Режим округления HALF_EVEN минимизирует смещение из-за операций округления. Он часто используется. Это также известно как округление банкира .

MathContext инкапсулирует как точность, так и режим округления . Есть несколько предопределенных MathContexts:

  • DECIMAL32 - точность 7 цифр и режим округления HALF_EVEN
  • DECIMAL64 - точность 16 цифр и режим округления HALF_EVEN
  • DECIMAL128 — точность до 34 цифр и режим округления HALF_EVEN
  • UNLIMITED – арифметика с неограниченной точностью

Используя этот класс, мы можем округлить число 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 представляет неизменяемые целые числа произвольной точности . Он похож на примитивные целочисленные типы, но допускает произвольные большие значения.

Он используется, когда вовлеченные целые числа превышают предел длинного типа. Например, факториал 50 равен 304140932017133780436126081660647688443776415689605120000000000000. Это значение слишком велико для обработки данных типа 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 реализует все арифметические и логические операции. Но это не перегружает операторов.

Он также реализует соответствующие методы класса Math : abs , min , max , pow , signum .

Мы сравниваем значение двух BigInteger с помощью метода 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 .