ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BigDecimal ❓
    ☕️ java 2023. 3. 7. 11:05

    ❓BigDecimal


    🧐 사용하는 이유

    @Test
    void test() {
      double a = 100.0000000005;
      double b = 10.0000000004;
    
      assertThat(a - b).isEqualTo(90.0000000001);
    }

     

    결과

    expected: 90.0000000001
    but was: 90.00000000009999

     

    📖 왜 실패를 할까?

    • 자바는 IEEE 754 부동 소수점 방식을 사용한다.
    • 따라서, 정확한 실수를 저장하지 않고 최대한 완벽에 가깝기 바라는 근사치 값을 저장한다.
    • 금융권 같은 사소한 값 차이가 중요한 곳에서 double 타입을 사용하게 되면 문제가 될 수 있다

     

    이런 문제점을 해결하기 위해 부동 소수점 방식이 아닌 정수를 이용해 실수를 표현하는

    java.math.BigDecimal 클래스를 사용한다.

     

    Java 언어에서 돈과 소수점을 다룬다면 BigDecimal을 사용하는 것이 필수!!

     

    BigDecimal 사용하기❗


    public class BigDecimal extends Number implements Comparable<BigDecimal> {
    
        private final BigInteger intVal;
    
        private final int scale;  
      
        private transient int precision;
    
        private transient String stringCache;
        
        ...
    }
    • intVal : 정수 부분
    • scale : 지수
      • 소수점 첫째 자리부터 0이 아닌 수로 끝나는 위치까지의 소수점 자릿수
    • precision : 정밀도
      • 정확히는 0이 아닌 수가 시작하는 위치부터 오른쪽부터 0이 아닌 수로 끝나는 위치까지의 총 자릿수

     

    테스트

    @Test
    void test() {
      BigDecimal bigDecimal = new BigDecimal("123.456");
    
      System.out.println(bigDecimal.unscaledValue());
      System.out.println(bigDecimal.scale());
      System.out.println(bigDecimal.precision());
    
      assertThat(bigDecimal).isEqualTo(BigDecimal.valueOf(123.456));
    }

    결과

    > 123456
    > 3
    > 6
    

     

    생성

    BigDecimal의 생성 방법은 여러 가지이다.

    • 문자열로 숫자를 표현하는 것이 일반적

    기본형 리터럴로는 표현할 수 있는 값에는 한계가 존재

    • ex) double 타입의 값을 그대로 전달할 경우 예상과 다른 값을 얻을 수 있다.

     

    예시

    BigDecimal bigDecimal;
    
    bigDecimal = new BigDecimal("1234.5678");
    bigDecimal = new BigDecimal(1234.5678); // 오차 발생 가능 있다.
    bigDecimal = new BigDecimal(12345);
    
    bigDecimal = BigDecimal.valueOf(1234.5678); // 오차 발생 가능 있다.
    bigDecimal = BigDecimal.valueOf(12345);
    // bigDecimal = BigDecimal.valueOf("1234.5678); // 불가
    

     

    📖 valueOf()

    BigDecimal은 생성자정적 팩토리 메서드valueOf()를 사용해 객체를 생성할 수 있다!

     

    BigDecimal은 일부 값을 캐싱해 놓고 있다.

    // Cache of common small BigDecimal values.
    private static final BigDecimal ZERO_THROUGH_TEN[] = {
         new BigDecimal(BigInteger.ZERO,       0,  0, 1),
         new BigDecimal(BigInteger.ONE,        1,  0, 1),
         new BigDecimal(BigInteger.TWO,        2,  0, 1),
         new BigDecimal(BigInteger.valueOf(3), 3,  0, 1),
         new BigDecimal(BigInteger.valueOf(4), 4,  0, 1),
         new BigDecimal(BigInteger.valueOf(5), 5,  0, 1),
         new BigDecimal(BigInteger.valueOf(6), 6,  0, 1),
         new BigDecimal(BigInteger.valueOf(7), 7,  0, 1),
         new BigDecimal(BigInteger.valueOf(8), 8,  0, 1),
         new BigDecimal(BigInteger.valueOf(9), 9,  0, 1),
         new BigDecimal(BigInteger.TEN,        10, 0, 2),
    };
    
    // Cache of zero scaled by 0 - 15
    private static final BigDecimal[] ZERO_SCALED_BY = {
         ZERO_THROUGH_TEN[0],
         new BigDecimal(BigInteger.ZERO, 0, 1, 1),
         new BigDecimal(BigInteger.ZERO, 0, 2, 1),
         new BigDecimal(BigInteger.ZERO, 0, 3, 1),
         new BigDecimal(BigInteger.ZERO, 0, 4, 1),
         new BigDecimal(BigInteger.ZERO, 0, 5, 1),
         new BigDecimal(BigInteger.ZERO, 0, 6, 1),
         new BigDecimal(BigInteger.ZERO, 0, 7, 1),
         new BigDecimal(BigInteger.ZERO, 0, 8, 1),
         new BigDecimal(BigInteger.ZERO, 0, 9, 1),
         new BigDecimal(BigInteger.ZERO, 0, 10, 1),
         new BigDecimal(BigInteger.ZERO, 0, 11, 1),
         new BigDecimal(BigInteger.ZERO, 0, 12, 1),
         new BigDecimal(BigInteger.ZERO, 0, 13, 1),
         new BigDecimal(BigInteger.ZERO, 0, 14, 1),
         new BigDecimal(BigInteger.ZERO, 0, 15, 1),
    };

     

    ❓캐싱을 한 이유

    • BigDecimal은 매우 큰 정밀도의 수를 다루는 클래스이다.
    • 이런 큰 수들은 많은 bit를 사용해 메모리 사용량이 매우 크다.
    • Java에서는 객체 생성 비용이 많이 들기 때문에 미리 정의된 범위 내의 값을 캐시해 해당 값이 필요할 때 매번 새로운 객체를 생성하는 것이 아닌 캐시 된 값을 사용해 객체 생성 비용을 줄인다.
    • 이렇게 BigDecimal 객체의 재사용을 가능하게 하고, 생성 비용을 줄이며, 메모리 사용량을 감소시켜 성능을 향상할 수 있다.
    • 또한, 내부 캐시를 사용해 값이 같은 BigDecimal 객체들을 같은 메모리 영역에서 공유할 수 있다.

     

    valueOf()

    public static BigDecimal valueOf(long val) {
         if (val >= 0 && val < ZERO_THROUGH_TEN.length)
             return ZERO_THROUGH_TEN[(int)val];
         else if (val != INFLATED)
             return new BigDecimal(null, val, 0, 0);
         return new BigDecimal(INFLATED_BIGINT, val, 0, 0);
     }

    정적 팩토리 메서드 valueOf()는 static 변수인 캐싱 값에 바로 접근할 수 있다.

    • 캐싱되어 있는 값이 존재한다면 생성자를 통해 생성하지 않고 바로 그 값을 반환한다.

     

    🆚 new BigDecimal()와 valueOf()

     

    1. 생성자

    • 생성자는 새로운 BigDecimal 객체를 생성한다.
    • 생성자는 매개 변수로 부동 소수점(double)이나 문자열(String) 값을 받을 수 있다.
    // 문자열 값을 이용하여 BigDecimal 객체 생성
    BigDecimal bd1 = new BigDecimal("123.456");
    // double 값을 이용하여 BigDecimal 객체 생성
    BigDecimal bd2 = new BigDecimal(123.456); 
    

     

     

    2. valueOf()

    • 기존 BigDecimal 객체를 재사용
    • 매개변수로 long, String 값을 받을 수 있다.
    • 내부 캐시에서 값을 가져와 객체를 생성해, 객체 생성 비용을 줄일 수 있다.
    // 문자열 값을 이용하여 BigDecimal 객체 생성
    BigDecimal bd1 = BigDecimal.valueOf("123.456"); 
    BigDecimal bd2 = BigDecimal.valueOf(123L);

     

    각각 언제 사용할까?

     

    1. BigDecimal 객체를 생성

    • 생성자 사용!
    • 이유는 문자열을 입력받아도 새로운 객체를 생성하기 때문에 객체의 불변성을 보장

    🔍 문자열을 입력받아도 새로운 객체를 생성

    • 생성자는 매개변수로 문자열을 받을 수 있다.
    • 문자열로 표현된 숫자를 BigDecimal 객체로 변환한다.
      • BigDecimal 생성자는 문자열을 분석해 새로운 BigDecimal 객체를 생성
      • 이때, 생성된 BigDecimal 객체는 문자열의 값과 동일한 값을 가진다.
    • 따라서, BigDecimal 생성자는 매번 새로운 객체를 생성한다.
      • 문자열로 표현된 숫자를 매번 BigDecimal 객체로 변환할 때마다, 새로운 객체가 생성되어 객체의 불변성을 보장!!

     

    BigDecimal bd1 = new BigDecimal("123.456");
    BigDecimal bd2 = new BigDecimal("123.456");
    System.out.println(bd1.equals(bd2)); // true
    
    • 두 개의 BigDecimal 객체는 문자열 “123.456” 으로 생성되었고 두 객체가 같은 값을 가진다.
    • 따라서 equals() 메서드는 true를 반환
    • 이것은 두 객체가 동일한 메모리 위치를 참조하고 있음을 의미하지는 않는다 ❌
    • 즉, 두 객체는 서로 다른 메모리 위치에 저장되어 있지만, 값이 동일해 불변성을 보장할 수 있다.

     

    2. 연산 수행 시

    • valueOf() 메서드를 사용
    • 내부 캐시에서 값을 가져와 객체를 생성하기 때문에 객체 생성 비용을 줄일 수 있다.
    • 또, 이미 존재하는 BigDecimal 객체를 재사용해 메모리 사용량을 줄인다.

     

    비교

    BigDeimalComparable <BigDecimal>를 구현하고 있다.

    public class BigDecimal extends Number implements Comparable<BigDecimal> {
      ...
    }
    
    • equals()를 통해 내용을 비교할 수 있다.

     

    ⚠️ 주의

    • 12.01 12.010
    @Test
    void test() {
      assertThat(new BigDecimal("12.01")).isEqualTo(new BigDecimal("12.010"));
    }
    

     

    expected: 12.010
    but was: 12.01
    

     

    타입 변환

    📖 문자열로 변환

    • toPlainString(); - 다른 기호 없이 숫자로만 표현
    • toString(); - 필요시 지수 형태로 표현할 수 있다.
    @Test
    void test() {
      BigDecimal bigDecimal = new BigDecimal(1.0e-22);
    
      System.out.println(bigDecimal.toString()); // 1.000000000000000048...5E-22
      System.out.println(bigDecimal.toPlainString());// 0.00000000000000000000010...41937255859375
    }

     

    📖 Number의 기본형으로 변환

    • intValue()
    • longValue()
    • floatValue()
    • doubleValue()
    @Test
    void test() {
    
      BigDecimal bigDecimal = new BigDecimal("123.45");
        
      assertThat(bigDecimal.doubleValue()).isEqualTo(123.45);
    
      assertThatThrownBy(bigDecimal::intValueExact)
          .isInstanceOf(ArithmeticException.class);
    }
    • 위 4가지 메서드의 이름 끝에 Exact가 붙은 것은 변환 결과가 변환 타입 범위에 속하지 않은 경우 ArithemticException을 발생시킨다.

     

    📌 연산


    기본적인 연산을 수행하는 메서드

    • add(BigDecimal value);
    • subtract(BigDecimal value);
    • multiply(BigDecimal value);
    • divide(BigDecimal value);
    • remainder(BigDecimal value);
    @Test
    void test() {
      BigDecimal bigDecimal1 = new BigDecimal("6.0");
      BigDecimal bigDecimal2 = new BigDecimal("3.0");
    
      BigDecimal sum = bigDecimal1.add(bigDecimal2);
      BigDecimal gap = bigDecimal1.subtract(bigDecimal2);
      BigDecimal multiply = bigDecimal1.multiply(bigDecimal2);
      BigDecimal divide = bigDecimal1.divide(bigDecimal2);
    
      assertThat(sum.compareTo(new BigDecimal("9.0")) == 0).isTrue();
      assertThat(gap.compareTo(new BigDecimal("3.0")) == 0).isTrue();
      assertThat(multiply.compareTo(new BigDecimal("18.0")) == 0).isTrue();
      assertThat(divide.compareTo(new BigDecimal("2.0")) == 0).isTrue();
    }

    ⚠️ BigDecimal 은 불변객체

    • 따라서, 연산 후 반환 타입이 BigDecimal인 경우 → 새로운 인스턴스가 반환된다.

     

    참고자료

    https://www.baeldung.com/java-bigdecimal-biginteger

    [Java] BigDecimal에 관한 고찰 🕵️‍♀️

    https://inpa.tistory.com/entry/JAVA-☕-BigInteger-BigDecimal-자료형-사용법-총정리

    '☕️ java' 카테고리의 다른 글

    DAO DTO / VO  (0) 2023.03.10
    [Java] 실수 표현 - 고정소수점, 부동소수점  (0) 2023.03.07
    [Java] String / StringBuffer / StringBuilde  (0) 2023.03.03
    [Java] Static 과 Final  (0) 2023.03.03
    Reflection API ?  (0) 2023.02.09

    댓글

Designed by Tistory.