BigDecimal và BigInteger trong Java

1. Khái quát chung

Trong hướng dẫn này, chúng tôi sẽ trình bày các lớp BigDecimalBigInteger .

Chúng tôi sẽ mô tả hai loại dữ liệu, đặc điểm của chúng và các tình huống sử dụng của chúng. Chúng tôi cũng sẽ trình bày ngắn gọn các thao tác khác nhau bằng cách sử dụng hai lớp.

2. BigDecimal

BigDecimal đại diện cho một số thập phân có dấu có độ chính xác tùy ý bất biến . Nó bao gồm hai phần:

  • Giá trị không được chia tỷ lệ - số nguyên chính xác tùy ý
  • Tỷ lệ - số nguyên 32 bit biểu thị số chữ số ở bên phải dấu thập phân

Ví dụ: BigDecimal 3,14 có giá trị chưa được chia tỷ lệ là 314 và tỷ lệ là 2.

Chúng tôi sử dụng BigDecimal cho số học chính xác cao. Chúng tôi cũng sử dụng nó cho các tính toán yêu cầu kiểm soát tỷ lệ và hành vi làm tròn số . Một ví dụ như vậy là các phép tính liên quan đến các giao dịch tài chính.

Chúng ta có thể tạo một đối tượng BigDecimal từ Chuỗi , mảng ký tự, int , longBigInteger :

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

Chúng tôi cũng có thể tạo BigDecimal từ double :

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

Tuy nhiên, kết quả, trong trường hợp này, khác với dự kiến ​​(đó là 0,1). Điều này là do:

  • hàm tạo kép thực hiện một bản dịch chính xác
  • 0,1 không có đại diện chính xác ở dạng kép

Do đó, chúng ta nên sử dụng chỉ số S Tring constructor thay cho đôi constructor .

Ngoài ra, chúng ta có thể chuyển đổi doublelong thành BigInteger bằng cách sử dụng phương thức 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()); }

Phương thức này chuyển đổi gấp đôi thành biểu diễn Chuỗi của nó trước khi chuyển đổi thành BigDecimal . Ngoài ra, nó có thể sử dụng lại các thể hiện đối tượng.

Do đó, chúng ta nên sử dụng phương thức valueOf ưu tiên cho các hàm tạo .

3. Các thao tác trên BigDecimal

Cũng giống như các lớp Number khác ( Integer , Long , Double , v.v.), BigDecimal cung cấp các phép toán cho các phép toán số học và so sánh. Nó cũng cung cấp các hoạt động để thao tác tỷ lệ, làm tròn và chuyển đổi định dạng.

Nó không quá tải các toán tử số học (+, -, /, *) hoặc logic (>. <Vv). Thay vào đó, chúng tôi sử dụng các phương pháp tương ứng - cộng , trừ , nhân , chia và so sánh .

BigDecimal có các phương pháp để trích xuất các thuộc tính khác nhau, chẳng hạn như độ chính xác, tỷ lệ và dấu :

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

Chúng tôi so sánh giá trị của hai BigDecimals bằng phương pháp 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); }

Phương pháp này bỏ qua thang đo trong khi so sánh.

Mặt khác, các bằng phương pháp xem xét hai BigDecimal đối tượng như bình đẳng chỉ khi họ đều bình đẳng về giá trị và quy mô . Do đó, BigDecimals 1.0 và 1.00 không bằng nhau khi so sánh bằng phương pháp này.

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

Chúng tôi thực hiện các phép toán số học bằng cách gọi các phương thức tương ứng :

@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 là bất biến, các thao tác này không sửa đổi các đối tượng hiện có. Đúng hơn, chúng trả về các đối tượng mới.

4. Làm tròn và BigDecimal

By rounding a number, we replace it with another having shorter, simpler and more meaningful representation. For example, we round $24.784917 to $24.78 as we do not have fractional cents.

The precision and rounding mode to be used varies depending on the calculation. For example, U.S. Federal Tax returns specify to round off to whole dollar amounts using HALF_UP.

There are two classes which control rounding behavior – RoundingMode and MathContext.

The enum RoundingMode provides eight rounding modes:

  • CEILING – rounds towards positive infinity
  • FLOOR – rounds towards negative infinity
  • UP – rounds away from zero
  • DOWN – rounds towards zero
  • HALF_UP – rounds towards “nearest neighbor” unless both neighbors are equidistant, in which case rounds up
  • HALF_DOWN – rounds towards “nearest neighbor” unless both neighbors are equidistant, in which case rounds down
  • HALF_EVEN – rounds towards the “nearest neighbor” unless both neighbors are equidistant, in which case, rounds towards the even neighbor
  • UNNECESSARY – no rounding is necessary and ArithmeticException is thrown if no exact result is possible

HALF_EVEN rounding mode minimizes the bias due to rounding operations. It is frequently used. It is also known as the banker's rounding.

MathContext encapsulates both precision and rounding mode. There are few predefined MathContexts:

  • DECIMAL32 – 7 digits precision and a rounding mode of HALF_EVEN
  • DECIMAL64 – 16 digits precision and a rounding mode of HALF_EVEN
  • DECIMAL128 – 34 digits precision and a rounding mode of HALF_EVEN
  • UNLIMITED – unlimited precision arithmetic

Using this class, we can round a BigDecimal number using specified precision and rounding behavior:

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

Now, let's examine the rounding concept using a sample calculation.

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 có các phép toán bit tương tự như intlong . Tuy nhiên, chúng ta cần sử dụng các phương thức thay vì các toán tử:

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

Nó có các phương pháp thao tác bit bổ sung :

@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 cung cấp các phương pháp tính toán GCD và số học mô-đun :

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

Nó cũng có các phương pháp liên quan đến kiểm tra tính nguyên tố và tính nguyên thủy :

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

7. Kết luận

Trong hướng dẫn nhanh này, chúng tôi đã khám phá các lớp BigDecimalBigInteger. Chúng hữu ích cho các tính toán số nâng cao mà các kiểu số nguyên nguyên thủy không đủ.

Như thường lệ, bạn có thể tìm thấy mã nguồn đầy đủ trên GitHub.