반응형

발생하는 이유


BigDecimal은 정확한 소수 연산을 하기 위해서 존재한다.

컴퓨터는 모든 것을 이진으로 변환하여 작업을 하는데 0.1인 1/10을 이전으로 바꾸면 무한 소수가 된다.

컴퓨는 float, double인 소수를 부동 소수점으로 표현하는데 부동 소수점은 오차가 생긴다는 단점이 있지만,

수 많은 데이터를 메모리에 올려 놓고 효율적으로 메모리를 사용해야 하는 컴퓨터는 부동 소수점 방식을 택하게 되었다.

고정 소수점을 사용하면 정확한 계산이 가능하겠지만, 무한 소수는 메모리를 무한으로 사용해야 하기 때문에 불가능하다.

그래서 정확한 10진수 계산을 위해서 BigDecimal을 Java에서 제공한다.

그런데 소수를 계산하는데 있어서 소수 정밀도는 어떻게 할 것이고, 반올림은 어떻게 할 것인지를 알지 못하면,

그에 맞는 정확한 계산을 BigDecimal도 할 수가 없게 된다. 그렇기 때문에 이와 같은 예외가 발생하게 된다.

또한, 하필 divide() 메서드에서만 이 예외가 발생하는 이유는 나눌 경우에 무한 소수가 발생하기 떄문이다.

발생 조건


divide() 메서드에 나눌 값만 넣을 경우에 발생하게 된다.

단, 이진수로 변환했을 때 무한 소수일 경우만 해당한다.

// given
BigDecimal one = BigDecimal.valueOf(1);   // 1
BigDecimal three = BigDecimal.valueOf(3); // 3
System.out.println("one = " + one);
System.out.println("three = " + three);

// when
BigDecimal divide = one.divide(three);

// Non-terminating decimal expansion; no exact representable decimal result.

소수 설정 해주어 해결


@Test
void 기본() {
    // given
    BigDecimal one = BigDecimal.valueOf(1);   // 1
    BigDecimal three = BigDecimal.valueOf(3); // 3

    // when
    BigDecimal divide = one.divide(three, 2, RoundingMode.HALF_UP);

    // then
    assertThat(divide.floatValue()).isEqualTo(0.33f);
}

위와 같이 when 절의 코드처럼 소수점 정밀도(scale)인 2와 정확하게 반올림될

반올림 모드(RoundingMode.HALF_UP)를 지정해주면 된다.

setScale 메서드

이 메서드는 정밀도를 셋팅하는 것이다. 기본값은 0이다.

그래서 1을 아래와 같이 scale을 셋팅하면 1.000이 나오게 된다.

@Test
void setScale() {
    // given
    BigDecimal one = BigDecimal.valueOf(1).setScale(3);   // 1.000
    BigDecimal three = BigDecimal.valueOf(3);             // 3
    System.out.println("one = " + one);
    System.out.println("three = " + three);

    // when
    BigDecimal divide = one.divide(three, RoundingMode.HALF_UP);

    // then
    assertThat(divide.floatValue()).isEqualTo(0.333f);
}

여기서 주의 사항이 몇가지 있다.

  1. then절을 보면 결과가 0.33이 아니라 0.333이 나오는데 이유는 divide시 scale을 설정하지 않아서
    one의 scale인 3으로 값을 갖기 때문이다.

  2. divide에 scale을 셋팅하면 setScale했던 값은 override된다.

  3. scale을 0으로 하면, 계산되지 않고, 0.0으로 계산된다.

     @Test
         void setScale() {
             // given
             BigDecimal one = BigDecimal.valueOf(1);   // 1.000
             BigDecimal three = BigDecimal.valueOf(3);             // 3
             System.out.println("one = " + one);
             System.out.println("three = " + three);
    
             // when
             BigDecimal divide = one.divide(three, RoundingMode.HALF_UP);
    
             // then
             assertThat(divide.floatValue()).isEqualTo(0.0f);
         }

float을 사용하면 안될까?


BigDecimal 말고 그냥 float으로 변환해서 나누면 안될까?

@Test
void float11() {
    // given
    float one = BigDecimal.valueOf(1).floatValue();
    float three = BigDecimal.valueOf(3).floatValue();
    System.out.println("one = " + one);
    System.out.println("three = " + three);

    // when
    float divide = one / three;

    // then
    assertThat(divide).isEqualTo(0.33333334f);
}

위에서 설명하나 바와 같이 부동 소수점을 사용하여, 약 7자리를 표현할 수 있는 float은 이와 같이

정밀한 조작을 할 수 없다.

반응형
복사했습니다!