이번 글은 실무에서 가장 많이 실수하는 클래스 중 하나이다.
특히 금융, 쇼핑몰, 결제 시스템에서는 거의 필수이며,
Java 면접에서도 자주 등장한다.
많은 사람들이
double price = 0.1 + 0.2;
System.out.println(price);
의 결과가
0.3
일 것이라고 생각한다.
하지만 실제 결과는
0.30000000000000004
이다.
왜 이런 일이 발생할까?
BigDecimal을 사용하는 이유
BigDecimal은 부동소수점 오차 없이 정확한 십진수 계산을 위한 클래스이다.
먼저 double부터 이해하자.
많은 사람들이
double value = 0.1;
을
0.1
이 그대로 저장된다고 생각한다.
하지만 실제로는 아니다.
컴퓨터는 2진수만 이해한다.
컴퓨터는
0
1
두 개의 숫자만 저장할 수 있다.
정수는 쉽게 표현된다.
10(decimal)
↓
1010(binary)
하지만
0.1(decimal)
은 어떨까?
0.1을 2진수로 바꾸면
0.1
↓
0.0001100110011001100110011...
처럼 끝없이 반복된다.
마치
1 / 3
=
0.333333333...
이 끝없이 반복되는 것과 같다.
그런데 메모리는 유한하다.
double은
64bit만 사용할 수 있다.
그래서
0.000110011001100110011001100110011...
을
0.0001100110011001100110011001100110
정도에서 잘라 저장한다.
즉,
실제로 저장되는 값은
0.1
≠
0.1000000000000000000
≈
0.10000000000000000555...
이다.
그래서 0.1 + 0.2
System.out.println(0.1 + 0.2);
실제로는
0.10000000000000000555...
+
0.20000000000000001110...
=
0.30000000000000004440...
이다.
출력
0.30000000000000004
실무에서 얼마나 위험할까?
쇼핑몰을 생각해 보자.
상품
0.1원
+
0.2원
=
0.3원
이어야 한다.
그런데
0.30000000000000004원
이 되면
세금 계산
↓
할인 계산
↓
총액 계산
을 거치면서 오차가 계속 누적된다.
금융 시스템에서는 절대 허용되지 않는다.
그래서 BigDecimal이 등장했다.
BigDecimal은
2진수
×
이 아니라
십진수 자체를
문자열 형태로 관리
+
정확한 계산
을 수행한다.
예제
BigDecimal a =
new BigDecimal("0.1");
BigDecimal b =
new BigDecimal("0.2");
System.out.println(a.add(b));
결과
0.3
정확하게 나온다.
내부적으로는 어떻게 저장될까?
double
0.1
↓
2진수 근사값
BigDecimal
"0.1"
↓
정확한 십진수 표현
↓
BigInteger + scale
로 저장한다.
BigDecimal 내부 구조
실제로는
대략 이런 형태이다.
BigDecimal
------------------
unscaledValue
scale
------------------
예를 들어
123.45
는
unscaledValue = 12345
scale = 2
로 저장된다.
즉,
12345
↓
소수점 두 자리
↓
123.45
이다.
가장 많이 하는 실수
다음 코드를 보자.
BigDecimal value =
new BigDecimal(0.1);
많은 사람들이
0.1
이 저장될 것이라고 생각한다.
하지만
0.1(double)은 이미
0.10000000000000000555...
이다.
그래서
System.out.println(
new BigDecimal(0.1)
);
결과는
0.100000000000000005551115...
처럼 출력된다.
올바른 방법
항상
new BigDecimal("0.1")
또는
BigDecimal.valueOf(0.1)
를 사용한다.
BigDecimal은 immutable이다.
String처럼
수정되지 않는다.
BigDecimal a =
new BigDecimal("10");
a.add(
new BigDecimal("5")
);
System.out.println(a);
결과
10
이다.
왜?
add()는
새 객체를 반환한다.
10
↓
add(5)
↓
15(new object)
이다.
그래서
a = a.add(
new BigDecimal("5")
);
처럼 다시 받아야 한다.
equals() 함정
다음 코드를 보자.
BigDecimal a =
new BigDecimal("1.0");
BigDecimal b =
new BigDecimal("1.00");
System.out.println(a.equals(b));
결과는 무엇일까?
많은 사람들이
true
라고 생각한다.
실제 결과
false
이다.
왜냐하면
BigDecimal의 equals는
값
+
scale
까지 비교한다.
1.0
↓
value=10
scale=1
1.00
↓
value=100
scale=2
scale이 다르므로
equals는 false이다.
compareTo()
실무에서는
a.compareTo(b)
를 많이 사용한다.
a.compareTo(b) == 0
결과
true
이다.
compareTo는
순수 값만 비교
하기 때문이다.
연산도 메서드이다.
BigDecimal은
연산자를 사용할 수 없다.
더하기
a.add(b);
빼기
a.subtract(b);
곱하기
a.multiply(b);
나누기
a.divide(b);
divide()도 함정이 있다.
BigDecimal.ONE.divide(
new BigDecimal("3")
);
결과
ArithmeticException
이다.
왜냐하면
1 / 3
=
0.333333333...
무한소수이기 때문이다.
그래서
실무에서는
BigDecimal.ONE.divide(
new BigDecimal("3"),
2,
RoundingMode.HALF_UP
);
처럼 사용한다.
결과
0.33
이다.
실무에서는 언제 사용할까?
금액
price
tax
discount
totalPrice
환율
USD
KRW
JPY
정밀 계산
금융
회계
정산
포인트
언제 double을 사용할까?
센서 값
온도
습도
거리
그래픽
좌표
게임
물리 계산
AI
행렬 계산
벡터 계산
여기서는 속도가 중요하므로
double을 사용한다.
그림으로 정리
double
0.1
↓
2진수 변환
↓
근사값 저장
↓
오차 가능
BigDecimal
"0.1"
↓
정확한 십진수 저장
↓
정확한 계산
↓
금융 시스템 사용
면접 포인트
BigDecimal을 사용하는 이유는?
double은 IEEE 754 부동소수점 방식으로 저장되어 오차가 발생할 수 있기 때문에, 정확한 십진수 계산이 필요한 경우 BigDecimal을 사용한다.
왜 new BigDecimal(0.1)은 위험한가?
0.1(double) 자체가 이미 근사값이므로 그 오차까지 그대로 BigDecimal에 저장되기 때문이다.
실무에서 권장하는 생성 방법은?
new BigDecimal("0.1")
또는
BigDecimal.valueOf(0.1)
이다.
equals()와 compareTo() 차이는?
- equals() : 값 + scale 비교 (1.0 ≠ 1.00)
- compareTo() : 순수 값만 비교 (1.0 = 1.00)
다음 글 추천
다음은 ThreadLocal 원리를 추천한다.
이 글부터는 다시 Spring과 강하게 연결된다.
- ThreadLocal이 무엇인지
- 왜 static처럼 보이는데 사용자마다 값이 다른지
- Spring Security의 사용자 정보
- TransactionSynchronizationManager
- MyBatis SqlSession
- @Transactional
이 모든 것이 하나의 ThreadLocal 개념으로 연결되는 것을 이해하게 된다.
'language > java' 카테고리의 다른 글
| ThreadLocal Memory Leak (0) | 2026.06.16 |
|---|---|
| ThreadLocal 원리 (0) | 2026.06.16 |
| StringBuilder vs StringBuffer (0) | 2026.06.16 |
| Integer Cache (0) | 2026.06.16 |
| AutoBoxing / Unboxing (0) | 2026.06.16 |
댓글