본문으로 건너뛰기
개발 머꼬
개발 노트Java
hohyeon.dev31

new BigDecimal(0.1)이 0.1이 아니어서 정산 금액이 어긋난 이유

  • #Engineering Note
  • #Java

문제 발생

정확한 계산을 위해 BigDecimal로 바꿨는데도 결과가 미세하게 어긋났습니다.

new BigDecimal(0.1);
// 0.1000000000000000055511151231257827021181583404541015625

원인 분석

BigDecimal의 문제가 아니라 인자로 넘긴 double이 이미 0.1이 아니라는 것이 문제입니다. 자바독이 그대로 설명합니다 — new BigDecimal(0.1)이 정확히 0.1인 값을 만들 것 같지만 실제로는 위의 긴 수와 같으며, 0.1은 double로(그리고 유한한 길이의 이진 분수로) 정확히 표현될 수 없기 때문입니다. 생성자에 전달되는 값 자체가 겉보기와 달리 0.1이 아닙니다.

BigDecimal(double) 생성자는 그 값을 정확히 보존합니다. 그래서 오차가 사라지기는커녕 눈에 보이게 됩니다. 자바독이 이 생성자의 결과를 "다소 예측하기 어렵다"고 표현하는 이유입니다.

BigDecimal.valueOf(double)은 다릅니다. Double.toString(double)의 결과를 거쳐 변환하므로 사람이 적은 값 그대로가 됩니다.

해결 방안

  1. 문자열이나 valueOf로 만듭니다.
new BigDecimal("0.1");        // 정확히 0.1
BigDecimal.valueOf(0.1);      // Double.toString을 거쳐 0.1

애초에 double을 거치지 않는 문자열 생성자가 가장 안전합니다.

  1. equals와 compareTo를 구분합니다. 이게 두 번째로 자주 걸리는 함정입니다.
new BigDecimal("0.10").equals(new BigDecimal("0.1"));      // false — 스케일이 다르다
new BigDecimal("0.10").compareTo(new BigDecimal("0.1"));   // 0     — 값이 같다

자바독의 설명대로 compareTo는 수치상 같으면 같다고 보고, equals는 값과 표현(스케일)이 모두 같아야 참입니다. 금액 비교에는 거의 항상 compareTo가 맞습니다.

  1. 나눗셈에는 반올림 모드를 지정합니다. 나누어떨어지지 않으면 ArithmeticException이 납니다.
amount.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);
  1. 불변이라는 점을 기억합니다. add/subtract는 새 객체를 돌려줍니다. 반환값을 받지 않으면 아무 일도 일어나지 않습니다.
total = total.add(price);     // total.add(price); 만 쓰면 total은 그대로다
  1. 저장 계층도 맞춥니다. DB 컬럼이 DOUBLE이면 애플리케이션에서 아무리 정확히 계산해도 저장·조회에서 오차가 다시 들어옵니다. DECIMAL을 씁니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.