Post

Java BigDecimal 및 BigInteger

1. BigDecimal

BigDecimal은 변경할 수 없는 임의 정밀도 부호 있는 십진수를 나타낸다. 두 부분으로 구성된다.

  • 스케일되지 않은 값: 임의의 정밀도 정수

  • 스케일: 소수점 오른쪽의 자릿수를 나타내는 32비트 정수

예를 들어, BigDecimal 3.14는 스케일 되지 않은 값이 314이고 스케일이 2이다.

고정밀 산술을 위해 BigDecimal 을 사용한다. 규모를 제어하고 동작을 반올림해야 하는 계산에도 사용한다. 그러한 예는 금융 거래와 관련된 계산이다.

String, character array, int, long, 그리고 BigInteger에서 BigDecimal 객체를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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());
}

Double에서 BigDecimal을 만들 수도 있다.

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

그러나 이 경우 결과는 예상과 다르다(즉, 0.1). 이유는 아래와 같다.

  • double 생성자는 정확한 표현 작업을 한다.

  • 0.1은 double에 정확한 표현이 없다.

따라서 double 생성자 대신 String 문자열 생성자를 사용해야 합니다 .

또한 valueOf 정적 메서드를 사용하여 double과 long을 BigDecimal로 변환할 수 있다.

1
2
3
4
5
6
7
8
9
10
@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());
}

이 메서드는 BigDecimal로 변환하기 전에 double을 String 표현으로 변환한다. 또한 개체 인스턴스를 재사용할 수 있다.

따라서 생성자보다 valueOf 메서드를 사용해야 한다.

2. BigDecimal 연산

다른 Number 클래스(Integer, Long, Double 등)와 마찬가지로 BigDecimal은 산술 및 비교 연산을 위한 연산을 제공한다. 또한 스케일 조작, 반올림 및 형식 변환을 위한 작업도 제공한다.

산술(+, -, /, *) 또는 논리(>, < 등) 연산자를 오버로드하지 않는다. 대신 더하기, 빼기, 곱하기, 나누기 및 compareTo와 같은 해당 메서드를 사용한다.

BigDecimal에는 정밀도, 스케일 및 부호와 같은 다양한 속성을 추출하는 메소드가 있다.

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

compareTo 메소드를 사용하여 두 BigDecimal의 값을 비교한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@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은 이 방법으로 비교할 때 같지 않다.

1
2
3
4
5
6
7
@Test
public void whenEqualsCalled_thenSizeAndScaleMatched() {
    BigDecimal bd1 = new BigDecimal("1.0");
    BigDecimal bd2 = new BigDecimal("1.00");
        
    assertFalse(bd1.equals(bd2));
}

해당 메소드를 호출하여 산술 연산을 수행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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은 불변이며, 이러한 작업은 기존 개체를 수정하지 않는다. 오히려 새 객체를 반환한다.

3. 반올림과 BigDecimal

숫자를 반올림하여 더 짧고 단순하며 의미 있는 표현을 가진 다른 숫자로 바꾼다. 예를 들어 분수 센트가 없기 때문에 $24.784917을 $24.78로 반올림한다.

사용하는 정밀도 및 반올림 모드는 계산에 따라 다르다. 예를 들어, 미국 연방 세금 환급은 HALF_UP을 사용하여 전체 달러 금액으로 반올림하도록 지정한다.

반올림 동작을 제어하는 두 가지 클래스인 RoundingMode 및 MathContext가 있다.

RoundingMode는 여덟개 반올림 모드를 제공한다.

  • CEILING: 양의 무한대로 반올림

  • FLOOR: 음의 무한대로 반올림

  • UP: 0에서 반올림

  • DOWN: 0으로 반올림

  • HALF_UP: 두 이웃이 등거리가 아닌 경우 “가장 가까운 이웃”으로 반올림한다. 이 경우 반올림한다.

  • HALF_DOWN: 두 이웃이 등거리가 아닌 경우 “가장 가까운 이웃”으로 반올림한다. 이 경우 내림한다.

  • HALF_EVEN: 두 이웃이 등거리가 아닌 경우 “가장 가까운 이웃”으로 반올림한다. 이 경우에는 짝수 이웃으로 반올림한다.

  • UNNECESSARY: 반올림이 필요 하지 않으며 정확한 결과가 가능하지 않으면 ArithmeticException이 발생한다.

HALF_EVEN 반올림 모드는 반올림 작업으로 인한 편향을 최소화한다. 자주 사용된다. 은행가의 반올림 이라고도 한다.

MathContex는 정밀도와 반올림 모드를 모두 캡슐화한다. 미리 정의된 MathContext는 거의 없다.

  • DECIMAL32: 7자리 정밀도 및 HALF_EVEN의 반올림 모드

  • DECIMAL64: 16자리 정밀도 및 HALF_EVEN의 반올림 모드

  • DECIMAL128: 34자리 정밀도 및 HALF_EVEN의 반올림 모드

  • UNLIMITED: 무제한 정밀 산술

이 클래스를 사용하여 지정된 정밀도 및 반올림 동작을 사용하여 BigDecimal 숫자를 반올림할 수 있다.

1
2
3
4
5
6
7
8
9
@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 메서드를 사용하여 최종 결과를 센트로 반올림한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

이 메서드에 대한 단위 테스트를 작성해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
@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());
}

4. BigInteger

BigInteger는 변경할 수 없는 임의 정밀도 정수를 나타낸다. 기본 정수 유형과 유사하지만 임의의 큰 값을 허용한다.

관련된 정수가 long 유형의 한계보다 클 때 사용된다. 예를 들어, 50의 계승은 30414093201713378043612608166064768844377641568960512000000000000이다. 이 값은 int 또는 long 데이터 유형이 처리하기에 너무 크다. BigInteger 변수에만 저장할 수 있다.

보안 및 암호화 응용 프로그램에서 널리 사용된다.

바이트 배열 또는 String에서 BigInteger를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@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());
}

또한 정적 메서드 valueOf를 사용하여 long을 BigInteger로 변환할 수 있다.

1
2
3
4
5
6
@Test
public void whenLongConvertedToBigInteger_thenValueMatches() {
    BigInteger bi =  BigInteger.valueOf(2305843009213693951L);
        
    assertEquals("2305843009213693951", bi.toString());
}

5. BigInteger에 대한 연산

int 및 long과 마찬가지로 BigInteger는 모든 산술 및 논리 연산을 구현한다. 그러나 연산자에 과부하를 주지 않습니다.

또한 Math 클래스의 해당 메소드(abs, min, max, pow, signum)를 구현 한다.

compareTo 메서드를 사용하여 두 BigIntegers의 값을 비교한다.

1
2
3
4
5
6
7
8
9
10
@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);
}

해당 메소드를 호출하여 산술 연산을 수행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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과 유사한 비트 연산이 있다. 그러나 연산자 대신 메서드를 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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);
}

추가 비트 조작 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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 계산 및 모듈식 산술을 위한 방법을 제공한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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);
}

또한 소수 생성 및 소수 테스트와 관련된 방법이 있다.

1
2
3
4
5
6
7
@Test
public void givenBigIntegers_whenPrimeOperations_thenExpectedResult() {
    BigInteger i = BigInteger.probablePrime(100, new Random());
        
    boolean isProbablePrime = i.isProbablePrime(1000);
    assertEquals(true, isProbablePrime);
}

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.