BigDecimal divide 메서드의 예외 발생 원인 분석

BigDecimal를 사용할 때 두 숫자의 나눗셈 연산에서 예외가 발생하는 경우가 있다. 일반적으로 정수나 실수 타입에서는 문제가 없지만, BigDecimal는 특수한 내부 구조로 인해 예상치 못한 ArithmeticException가 발생할 수 있다.

예제 코드를 살펴보면:

BigDecimal orderAmount = new BigDecimal(2312.23);
BigDecimal customerCount = new BigDecimal(35);
BigDecimal pricePerCustomer = orderAmount.divide(customerCount);

BigDecimal profit = (orderAmount.subtract(new BigDecimal(984.23)))
                    .divide(new BigDecimal(984.23));

첫 번째 나눗셈은 정상적으로 실행되지만, 두 번째 연산에서 다음과 같은 오류가 발생한다:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    at java.math.BigDecimal.divide(BigDecimal.java:1690)

이 오류의 핵심은 결과값이 무한소수이며, 정확한 표현이 불가능하다는 것이다. 즉, 소수점 이하 자릿수가 끝없이 계속되는 경우, 기본적으로 정밀도를 설정하지 않은 divide 메서드는 실패한다.

이 문제의 근본 원인은 BigDecimal 생성 시 사용된 방식에 있다. new BigDecimal(2312.23)처럼 double 값을 직접 전달하면, 이진 부동소수점의 표현 한계로 인해 정확한 값이 저장되지 않으며, 내부 스케일(scale)이 41로 설정된다. 반면 new BigDecimal(35)는 정수형으로 처리되어 스케일은 0이다.

이러한 차이로 인해 첫 번째 연산에서는 preferredScale이 41로 설정되며, 결과의 정밀도가 제한되지 않아 예외가 발생하지 않는다. 하지만 두 번째 연산에서는 피제수와 제수 모두 부동소수점 형태로 간주되며, MathContextprecision이 192로 설정되면서, 정확한 결과를 도출할 수 없는 상황이 된다.

특히 중요한 점은, divide(BigDecimal divisor) 메서드는 내부적으로 RoundingMode.UNNECESSARY를 기본값으로 사용하는데, 이는 반드시 정확한 결과가 필요하다는 의미다. 그러나 무한소수 계산에서는 이를 만족할 수 없으므로 예외가 발생한다.

따라서, 안전한 사용을 위해서는 다음 방식을 권장한다:

BigDecimal result = dividend.divide(divisor, 10, RoundingMode.HALF_UP);

여기서 scale은 결과의 소수점 자릿수를 지정하고, roundingMode는 반올림 규칙을 명시해야 한다. 이렇게 하면 무한소수 문제를 피하고, 예측 가능한 결과를 얻을 수 있다.

결론적으로, BigDecimal.divide(BigDecimal)는 비용이나 금융 계산 등 정밀도가 중요한 환경에서는 사용을 피해야 하며, 항상 정확한 스케일과 반올림 정책을 명시하는 것이 중요하다.

태그: BigDecimal java arithmetic exception rounding mode precision

7월 31일 07:42에 게시됨