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

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. equalscompareTo를 구분합니다. 이게 두 번째로 자주 걸리는 함정입니다.
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

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