new BigDecimal(0.1)이 0.1이 아니어서 정산 금액이 어긋난 이유
문제 발생
정확한 계산을 위해 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)의 결과를 거쳐 변환하므로 사람이 적은 값 그대로가 됩니다.
해결 방안
- 문자열이나
valueOf로 만듭니다.
new BigDecimal("0.1"); // 정확히 0.1
BigDecimal.valueOf(0.1); // Double.toString을 거쳐 0.1애초에 double을 거치지 않는 문자열 생성자가 가장 안전합니다.
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가 맞습니다.
- 나눗셈에는 반올림 모드를 지정합니다. 나누어떨어지지 않으면
ArithmeticException이 납니다.
amount.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);- 불변이라는 점을 기억합니다.
add/subtract는 새 객체를 돌려줍니다. 반환값을 받지 않으면 아무 일도 일어나지 않습니다.
total = total.add(price); // total.add(price); 만 쓰면 total은 그대로다- 저장 계층도 맞춥니다. DB 컬럼이
DOUBLE이면 애플리케이션에서 아무리 정확히 계산해도 저장·조회에서 오차가 다시 들어옵니다.DECIMAL을 씁니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.