
SOFTMOA TECHNOLOGY
Java BigDecimal 소수점 계산 오류와 반올림 설정 방법
소프트모아가 정리한 기술 기록입니다.
Contents
Java에서 소수를 더한 결과에 예상보다 많은 소수 자릿수가 붙거나, 같은 숫자처럼 보이는 값의 비교가 실패한다면 출력 형식부터 바꾸지 마세요. 입력을 어떤 타입으로 받았는지, 값의 같음을 어떻게 판단하는지, 어느 단계에서 반올림하는지를 나눠 확인해야 합니다. 십진 소수를 그대로 계산하려면 문자열로 BigDecimal을 만들고, 나눗셈에는 필요한 자릿수와 반올림 방식을 명시하는 것이 출발점입니다.[1]
입력 단계에서 이미 달라진 값인지 확인하기
예를 들어 사용자가 입력한 0.2와 0.4를 합산하는 기능을 생각해 봅시다. 아래 검사에서 double 덧셈은 0.6000000000000001을 출력하고, 문자열에서 만든 BigDecimal의 합은 0.6을 출력했습니다. 화면에 소수 한 자리만 보여 주면 차이를 숨길 수는 있지만, 이후 계산에 전달되는 값까지 고친 것은 아닙니다.
BigDecimal(double) 생성자는 이미 들어온 이진 부동소수점 값을 정확히 변환합니다. 그러므로 double로 먼저 계산한 뒤 감싸는 방식은 원래 입력한 십진 소수를 복원하는 방법이 아닙니다.[1] 입력 원문을 확보할 수 있다면 파싱 위치부터 점검하세요. 모든 수치 처리를 무조건 바꾸기보다, 정확한 십진 값이 필요한 경로를 좁혀 수정하는 편이 검토하기 쉽습니다.
한 파일로 계산·비교·나눗셈 재현하기
다음 코드를 DecimalCheck.java로 저장하세요. 외부 라이브러리나 DB 연결 없이 작은 값만 계산합니다. 이 예제는 Temurin OpenJDK 25.0.4.1에서 컴파일하고 실행해 아래 출력을 확인했습니다. 먼저 java -version과 javac -version으로 로컬 실행·컴파일 환경도 확인하세요.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class DecimalCheck {
public static void main(String[] args) {
System.out.println("double=" + (0.2 + 0.4));
BigDecimal sum = new BigDecimal("0.2").add(new BigDecimal("0.4"));
System.out.println("decimal=" + sum.toPlainString());
BigDecimal a = new BigDecimal("7.0");
BigDecimal b = new BigDecimal("7.00");
System.out.println("equals=" + a.equals(b));
System.out.println("compare=" + a.compareTo(b));
BigDecimal n = new BigDecimal("4.125");
BigDecimal rounded = n.setScale(2, RoundingMode.HALF_UP);
System.out.println("up=" + rounded);
System.out.println("even=" + n.setScale(2, RoundingMode.HALF_EVEN));
System.out.println("original=" + n);
System.out.println("ratio=" + new BigDecimal("2")
.divide(new BigDecimal("7"), 4, RoundingMode.HALF_UP));
try {
new BigDecimal("2").divide(new BigDecimal("7"));
} catch (ArithmeticException e) {
System.out.println("exact=" + e.getClass().getSimpleName());
}
}
}
파일을 저장한 폴더에서 다음 명령을 실행합니다.
javac DecimalCheck.java
java DecimalCheck
확인한 출력은 다음과 같습니다.
double=0.6000000000000001
decimal=0.6
equals=false
compare=0
up=4.13
even=4.12
original=4.125
ratio=0.2857
exact=ArithmeticException
equals=false가 계산 실패를 뜻하지는 않습니다
7.0과 7.00은 수치로는 같지만 소수 자릿수를 나타내는 scale이 다릅니다. BigDecimal의 equals는 값과 scale을 함께 비교하고, compareTo는 수치의 크기를 비교합니다.[1] 예제에서 equals=false와 compare=0이 동시에 나온 이유입니다. 수치가 같은지 판단하는 조건에는 compareTo의 결과가 0인지 확인하고, 입력 자릿수까지 같아야 하는 조건에는 그 기준을 따로 유지하세요.
반올림한 결과를 어느 변수에 담았는지도 봐야 합니다. 이 예제는 setScale의 반환값을 rounded에 저장해서 4.13을 출력하지만, 원래 변수 n은 4.125로 남았습니다. 반환값을 버린 채 원래 변수를 저장하면 수정이 반영되지 않았다고 오해할 수 있습니다.[1] 비교 조건을 바꾸는 작업과 결과 대입을 고치는 작업은 서로 다른 수정입니다.
반올림 방식과 적용 위치를 명세에 남기기
예제의 scale 2는 소수 둘째 자리까지, 나눗셈의 scale 4는 소수 넷째 자리까지 남긴다는 설정입니다. 4.125를 두 자리로 줄였을 때 HALF_UP은 4.13, HALF_EVEN은 4.12가 됐습니다. HALF_EVEN은 정확히 중간인 경우 남길 마지막 숫자가 짝수가 되는 쪽을 고릅니다.[2] 둘 중 하나를 관행으로 지정하기 전에 해당 기능의 계산 규칙과 기대값을 확인하세요.
정확한 결과만 요청한 2 ÷ 7은 유한한 소수로 끝나지 않아 ArithmeticException이 발생합니다.[1] 예제에서는 scale 4와 HALF_UP을 지정한 별도 호출이 0.2857을 반환했습니다. catch 블록은 실패를 재현하려고 넣었습니다. 실제 업무 코드에서 예외를 잡고 임의로 0을 저장하는 처리로 옮기지는 마세요.
- 입력 검증: 빈 문자열, 숫자가 아닌 문자, 허용 자릿수 초과를 계산 전에 거절할지 정합니다.
- 비교 검증: 7.0과 7.00을 같은 값으로 볼지, 입력 표현을 구분할지 테스트에 적습니다.
- 계산 검증: 양수·음수의 경계값과 0으로 나누는 요청에 대한 기대 동작을 따로 확인합니다.
- 저장 검증: 항목별 계산 직후 반올림할지, 합산 후 한 번 할지 명세에 기록하고 저장된 값까지 대조합니다.
이 작은 검사로 확인한 것은 Java의 계산 결과와 비교 방식입니다. API가 입력을 바꾸는지, DB 컬럼이 값을 잘라 저장하는지까지 검증한 것은 아닙니다. 운영 수정 전에는 입력 원문, 계산 직후 값, 저장 후 조회 값을 같은 테스트 건으로 이어 확인하세요. 소수점 표시만 바꾸는 수정과 계산 규칙을 바꾸는 수정을 분리해야 원인을 되짚을 수 있습니다.
확인 문서
소프트모아의 서비스와 포트폴리오는 softmoa.com에서 확인할 수 있습니다.
소프트모아는 해당 시스템을 구축합니다. 문의하기
