1. 소개
Java에서의 수치 계산 정밀도 문제
Java 프로그래밍에서는 수치 계산을 매일 수행합니다. 예를 들어, 제품 가격을 계산하거나 세금·이자를 산정하는 등은 많은 애플리케이션에서 필수적인 작업입니다. 그러나 float나 double과 같은 부동소수점 타입을 사용해 이러한 계산을 수행하면 예상치 못한 오류가 발생할 수 있습니다.
이는 float와 double이 값을 이진 근사값으로 표현하기 때문입니다. 십진법으로 정확히 표현할 수 있는 “0.1”이나 “0.2”와 같은 값도 이진법으로는 정확히 나타낼 수 없으며, 그 결과 작은 오차가 누적됩니다.
금전 계산이나 정밀 계산에 반드시 필요한 BigDecimal
이러한 오차는 금전 계산 및 정밀 과학·공학 계산 분야에서 치명적일 수 있습니다. 예를 들어 청구서 계산에서 1엔 차이만 발생해도 신뢰도에 큰 타격을 줄 수 있습니다.
이때 Java의 BigDecimal 클래스가 큰 역할을 합니다. BigDecimal은 임의 정밀도의 십진수를 처리할 수 있으며, float·double 대신 사용하면 수치 계산을 오류 없이 수행할 수 있습니다.
이 글을 통해 얻을 수 있는 것
본 문서에서는 Java에서 BigDecimal을 사용하는 기본 방법, 고급 기법, 그리고 흔히 발생하는 실수와 주의사항을 체계적으로 설명합니다.
Java에서 금전 계산을 정확히 처리하고 싶거나 프로젝트에 BigDecimal 도입을 고려하고 있는 분들에게 유용합니다.
2. BigDecimal이란?
BigDecimal 개요
BigDecimal은 고정밀 십진수 연산을 가능하게 하는 Java 클래스입니다. java.math 패키지에 속하며, 오차를 허용할 수 없는 금융·회계·세금 계산 등에 특화되어 설계되었습니다.
Java의 float·double은 숫자를 이진 근사값으로 저장하므로 “0.1”·“0.2”와 같은 십진수는 정확히 표현되지 않아 오류가 발생합니다. 반면 BigDecimal은 값을 문자열 기반 십진수 표현으로 저장하므로 반올림·근사 오류를 억제합니다.
임의 정밀도 숫자 다루기
BigDecimal의 가장 큰 특징은 “임의 정밀도”입니다. 정수부와 소수부 모두 이론적으로 사실상 무한한 자리수를 처리할 수 있어 자리수 제한에 의한 반올림이나 손실이 없습니다.
예를 들어 다음과 같은 큰 수도 정확히 다룰 수 있습니다:
BigDecimal bigValue = new BigDecimal("12345678901234567890.12345678901234567890");
이처럼 정밀도를 유지하면서 연산을 수행할 수 있는 것이 BigDecimal의 주요 강점입니다.
주요 사용 사례
BigDecimal은 다음과 같은 상황에서 권장됩니다:
- 금전 계산 — 금융 애플리케이션의 이자·세율 계산
- 청구서·견적 금액 처리
- 높은 정밀도가 요구되는 과학·공학 계산
- 장기간 누적으로 인해 오류가 축적되는 프로세스
예를 들어 회계 시스템 및 급여 계산에서는 1엔 차이만 발생해도 큰 손실이나 분쟁으로 이어질 수 있으므로 BigDecimal의 정밀도가 필수적입니다.
3. BigDecimal 기본 사용법
BigDecimal 인스턴스 생성 방법
일반 숫자 리터럴과 달리 BigDecimal은 문자열로부터 생성하는 것이 일반적입니다. double·float로 만든 값은 이미 이진 근사 오류를 포함하고 있을 수 있기 때문입니다.
권장 방식 (String으로 생성):
BigDecimal value = new BigDecimal("0.1");
피해야 할 방식 (double로 생성):
BigDecimal value = new BigDecimal(0.1); // may contain error
연산 수행 방법
BigDecimal은 일반 연산자(+, -, *, /)를 사용할 수 없습니다. 대신 전용 메서드를 사용해야 합니다.
덧셈 (add)
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");
BigDecimal result = a.add(b); // 12.8
뺄셈 (subtract)
BigDecimal result = a.subtract(b); // 8.2
곱셈 (multiply)
BigDecimal result = a.multiply(b); // 24.15
나눗셈 (divide) 및 반올림 모드
나눗셈은 주의가 필요합니다. 나누어떨어지지 않을 경우, 반올림 모드를 지정하지 않으면 ArithmeticException이 발생합니다.
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 3.33
여기서는 “소수점 2자리”와 “반올림(반올림 절반 올림)”을 지정합니다.
setScale를 사용한 스케일 및 반올림 모드 설정
setScale는 지정된 자리수로 반올림하는 데 사용할 수 있습니다.
BigDecimal value = new Big BigDecimal("123.456789");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46
일반적인 RoundingMode 값:
| Mode Name | Description |
|---|---|
HALF_UP | Round half up (standard rounding) |
HALF_DOWN | Round half down |
HALF_EVEN | Banker’s rounding |
UP | Always round up |
DOWN | Always round down |
BigDecimal은 불변(Immutable)입니다
BigDecimal은 불변입니다. 즉, 산술 메서드(add, subtract 등)는 원래 값을 수정하지 않고 새 인스턴스를 반환합니다.
BigDecimal original = new BigDecimal("5.0");
BigDecimal result = original.add(new BigDecimal("1.0"));
System.out.println(original); // still 5.0
System.out.println(result); // 6.0
4. BigDecimal 고급 사용법
값 비교: compareTo와 equals의 차이점
BigDecimal에서는 값을 비교하는 두 가지 방법이 있습니다: compareTo()와 equals(). 이 두 메서드는 동작이 다릅니다.
compareTo()는 숫자값만 비교하고 (스케일은 무시합니다).equals()는 스케일(소수점 자리수)까지 포함하여 비교합니다.BigDecimal a = new BigDecimal("10.0"); BigDecimal b = new BigDecimal("10.00"); System.out.println(a.compareTo(b)); // 0 (values are equal) System.out.println(a.equals(b)); // false (scale differs)
포인트: 금액과 같은 수치적 동등성 검사는 compareTo()를 일반적으로 권장합니다.
문자열과의 변환
사용자 입력 및 외부 파일 가져오기 시 String 타입과의 변환이 일반적입니다.
String → BigDecimal
BigDecimal value = new Big BigDecimal("1234.56");
BigDecimal → String
String str = value.toString(); // "1234.56"
valueOf 사용
Java에는 BigDecimal.valueOf(double val)도 있지만, 이 역시 내부적으로 double의 오차를 포함하므로 문자열로부터 생성하는 것이 여전히 안전합니다.
BigDecimal unsafe = BigDecimal.valueOf(0.1); // contains internal error
MathContext를 통한 정밀도 및 반올림 규칙
MathContext는 정밀도와 반올림 모드를 한 번에 제어할 수 있어, 여러 연산에 공통 규칙을 적용할 때 유용합니다.
MathContext mc = new MathContext(4, RoundingMode.HALF_UP);
BigDecimal result = new BigDecimal("123.4567").round(mc); // 123.5
연산에서도 사용할 수 있습니다:
BigDecimal a = new BigDecimal("10.456");
BigDecimal b = new BigDecimal("2.1");
BigDecimal result = a.multiply(b, mc); // 4-digit precision
null 검사 및 안전한 초기화
폼에서 null 또는 빈 값을 전달할 수 있으므로, 방어 코드를 작성하는 것이 일반적입니다.
String input = ""; // empty
BigDecimal value = (input == null || input.isEmpty()) ? BigDecimal.ZERO : new BigDecimal(input);
BigDecimal의 스케일 확인
소수점 자리수를 확인하려면 scale()을 사용합니다:
BigDecimal value = new BigDecimal("123.45");
System.out.println(value.scale()); // 3
5. 일반적인 오류와 해결 방법
ArithmeticException: 무한 소수 확장
오류 예시:
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b); // exception
이는 “1 ÷ 3”이며, 무한 소수가 되기 때문에 반올림 모드/스케일을 지정하지 않으면 예외가 발생합니다.
해결 방법: 스케일과 반올림 모드를 지정합니다.
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // OK (3.33)
double에서 직접 생성할 때 발생하는 오류
double을 직접 전달하면 이미 이진 오차가 포함될 수 있어 예상치 못한 값이 생성됩니다.
잘못된 예시:
BigDecimal val = new BigDecimal(0.1);
System.out.println(val); // 0.100000000000000005551115123...
정답: 문자열 사용
BigDecimal val = new BigDecimal("0.1"); // exact 0.1
참고: BigDecimal.valueOf(0.1) 은 내부적으로 Double.toString() 을 사용하므로 new BigDecimal("0.1") 와 “거의 동일”합니다 — 하지만 문자열을 사용하는 것이 100% 안전합니다. 
스케일 불일치로 인한 equals 오해
equals() 는 스케일을 비교하기 때문에, 값이 수치적으로 동일하더라도 false 를 반환할 수 있습니다.
BigDecimal a = new BigDecimal("10.0");
BigDecimal b = new BigDecimal("10.00");
System.out.println(a.equals(b)); // false
해결책: 수치적 동등성을 위해 compareTo() 사용
System.out.println(a.compareTo(b)); // 0
부정확한 정밀도 때문에 발생하는 예상치 못한 결과
setScale 를 반올림 모드 없이 사용하면 예외가 발생할 수 있습니다.
잘못된 예시:
BigDecimal value = new BigDecimal("1.2567");
BigDecimal rounded = value.setScale(2); // exception
해결책:
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // OK
입력 값이 유효하지 않을 때 발생하는 NumberFormatException
숫자로 파싱할 수 없는 잘못된 텍스트가 전달되면(예: 사용자 입력 / CSV 필드) NumberFormatException 이 발생합니다.
해결책: 예외 처리를 사용
try {
BigDecimal value = new BigDecimal(userInput);
} catch (NumberFormatException e) {
// show error message or fallback logic
}
6. 실용적인 사용 예시
여기서는 BigDecimal 을 실제로 어떻게 활용할 수 있는지 실제 시나리오 를 소개합니다. 특히 재무/회계/세금 계산에서 정확한 수치 처리가 얼마나 중요한지 명확해집니다.
가격 계산에서 소수점 처리 (소수 반올림)
예시: 10% 소비세를 포함한 가격 계산
BigDecimal price = new BigDecimal("980"); // price w/o tax
BigDecimal taxRate = new BigDecimal("0.10");
BigDecimal tax = price.multiply(taxRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal total = price.add(tax);
System.out.println("Tax: " + tax); // Tax: 98
System.out.println("Total: " + total); // Total: 1078
포인트:
- 세금 계산 결과는 종종 정수 로 처리되며,
setScale(0, RoundingMode.HALF_UP)를 사용해 반올림합니다. double은 오류를 일으키기 쉬우므로BigDecimal사용을 권장합니다.
할인 계산 (% OFF)
예시: 20% 할인
BigDecimal originalPrice = new BigDecimal("3500");
BigDecimal discountRate = new BigDecimal("0.20");
BigDecimal discount = originalPrice.multiply(discountRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal discountedPrice = originalPrice.subtract(discount);
System.out.println("Discount: " + discount); // Discount: 700
System.out.println("After discount: " + discountedPrice); // 2800
포인트: 가격 할인 계산은 정밀도를 잃어서는 안 됩니다.
단가 × 수량 계산 (일반 비즈니스 앱 시나리오)
예시: 298.5엔 × 7개
BigDecimal unitPrice = new BigDecimal("298.5");
BigDecimal quantity = new BigDecimal("7");
BigDecimal total = unitPrice.multiply(quantity).setScale(2, RoundingMode.HALF_UP);
System.out.println("Total: " + total); // 2089.50
포인트:
- 소수점 곱셈에 대한 반올림을 조정합니다.
- 회계/주문 시스템에서 중요합니다.
복리 계산 (재무 예시)
예시: 연 3% 이자 × 5년
BigDecimal principal = new BigDecimal("1000000"); // base: 1,000,000
BigDecimal rate = new BigDecimal("0.03");
int years = 5;
BigDecimal finalAmount = principal;
for (int i = 0; i < years; i++) {
finalAmount = finalAmount.multiply(rate.add(BigDecimal.ONE)).setScale(2, RoundingMode.HALF_UP);
}
System.out.println("After 5 years: " + finalAmount); // approx 1,159,274.41
포인트:
- 반복 계산에서 오류가 누적되는데, BigDecimal 이 이를 방지합니다.
사용자 입력의 검증 및 변환
public static BigDecimal parseAmount(String input) {
try {
return new BigDecimal(input).setScale(2, RoundingMode.HALF_UP);
} catch (NumberFormatException e) {
return BigDecimal.ZERO; // treat invalid input as 0
}
}
포인트:
- 사용자 제공 숫자 문자열을 안전하게 변환합니다.
- 검증 + 오류 대체가 견고성을 향상시킵니다.
7. 요약
BigDecimal의 역할
Java의 숫자 처리 — 특히 금전적 또는 정밀도 요구 로직 —에서 BigDecimal 클래스는 필수적입니다. float / double의 내재된 오류를 BigDecimal을 사용함으로써 극적으로 피할 수 있습니다.
이 기사는 기본 사항, 산술, 비교, 반올림, 오류 처리, 그리고 실세계 예제를 다루었습니다.
주요 검토 포인트
BigDecimal은 임의 정밀도 소수를 처리합니다 — 돈과 정밀도 수학에 이상적입니다- 초기화는 문자열 리터럴을 통해 수행해야 합니다. 예:
new BigDecimal("0.1") add(),subtract(),multiply(),divide()를 사용하고, 나누기 시 항상 반올림 모드를 지정합니다- 동등성을 위해
compareTo()를 사용합니다 —equals()와의 차이를 이해하세요 setScale()/MathContext를 통해 스케일 + 반올림을 세밀하게 제어할 수 있습니다- 실제 비즈니스 로직 사례로는 돈, 세금, 수량 × 단가 등이 있습니다.
BigDecimal을 사용하려는 분들을 위해
Java에서 “숫자 처리”는 간단해 보이지만 — 정밀도 / 반올림 / 숫자 오류 문제가 항상 뒤에 숨어 있습니다. BigDecimal은 이러한 문제를 직접적으로 해결하는 도구입니다 — 이를 마스터하면 더 신뢰할 수 있는 코드를 작성할 수 있습니다.
처음에는 반올림 모드에 어려움을 겪을 수 있지만, 실제 프로젝트 사용으로 자연스러워집니다.
다음 장은 BigDecimal에 대한 일반적인 질문을 요약한 FAQ 섹션입니다 — 검토와 특정 의미 검색에 유용합니다.
8. FAQ: BigDecimal에 대한 자주 묻는 질문
Q1. float나 double 대신 BigDecimal을 왜 사용해야 하나요?
A1. float/double은 숫자를 이진 근사치로 표현하기 때문입니다 — 소수 분수는 정확하게 표현할 수 없습니다. 이로 인해 “0.1 + 0.2 ≠ 0.3.” 같은 결과가 발생합니다. BigDecimal은 소수 값을 정확하게 보존합니다 — 돈이나 정밀도가 중요한 로직에 이상적입니다.
Q2. BigDecimal 인스턴스를 구성하는 가장 안전한 방법은 무엇인가요?
A2. 항상 문자열로부터 구성하세요. 잘못된 방법 (오류):
new BigDecimal(0.1)
올바른 방법:
new BigDecimal("0.1")
BigDecimal.valueOf(0.1)은 내부적으로 Double.toString()을 사용하므로 거의 동일하지만, 문자열이 가장 안전합니다.
Q3. divide()가 왜 예외를 발생시키나요?
A3. BigDecimal.divide()는 결과가 비종결 소수인 경우 ArithmeticException을 발생시키기 때문입니다. 해결책: 스케일 + 반올림 모드 지정
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
Q4. compareTo()와 equals()의 차이는 무엇인가요?
A4.
compareTo()는 숫자적 동등성을 확인합니다 (스케일 무시)equals()는 정확한 동등성을 확인합니다 (스케일 포함)new BigDecimal("10.0").compareTo(new BigDecimal("10.00")); // → 0 new BigDecimal("10.0").equals(new BigDecimal("10.00")); // → false
Q5. 반올림을 어떻게 수행하나요?
A5. 명시적 반올림 모드와 함께 setScale()을 사용하세요.
BigDecimal value = new BigDecimal("123.4567");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46
주요 반올림 모드:
RoundingMode.HALF_UP(반올림 업)RoundingMode.DOWN(내림)RoundingMode.UP(올림)
Q6. 소수 자릿수 (스케일)를 확인할 수 있나요?
A6. 네 — scale()을 사용하세요.
BigDecimal val = new BigDecimal("123.45");
System.out.println(val.scale()); // → 3
Q7. null/빈 입력을 안전하게 처리하는 방법은 무엇인가요?
A7. 항상 null 검사 + 예외 처리를 포함하세요.
public static BigDecimal parseSafe(String input) {
if (input == null || input.trim().isEmpty()) return BigDecimal.ZERO;
try {
return new BigDecimal(input.trim());
} catch (NumberFormatException e) {
return BigDecimal.ZERO;
}
}

