60. 정확한 답이 필요하다면 float와 double은 피하라
📝 아이템 60: 정확한 답이 필요하다면 float와 double은 피하라
🔹 핵심 요약
✅ float와 double은 근사치 계산에 적합하며 정확한 결과가 필요할 때는 적합하지 않음 ✅ 금융 계산 같은 정확한 계산에는 BigDecimal, int 또는 long을 사용해야 함 ✅ BigDecimal은 정확하지만 사용이 불편하고 느림 ✅ 성능이 중요하고 소수점을 직접 관리할 수 있다면 int나 long을 권장
📚 필수 개념 정리
🧮 부동소수점 vs 고정소수점 vs BigDecimal 비교
정밀도
제한적 (IEEE 754 표준)
소수점 직접 관리 (정수)
임의 정밀도 (무제한)
표현 범위
매우 넓음 (±1.7E±308)
제한적 (int: ~21억, long: ~922경)
사실상 무제한
정확성
부정확 (근사값)
정확 (범위 내에서)
정확 (십진수 표현)
성능
매우 빠름
빠름
느림 (~10배)
메모리
적음 (4/8바이트)
적음 (4/8바이트)
많음 (가변)
사용 편의성
쉬움
소수점 관리 필요
편리하지만 코드 복잡성 증가
적합한 용도
과학/공학, 센서값 계산
임베디드, 간단한 금융 계산(소규모)
금융/정밀 수치 계산
오차 발생
필연적
없음 (적절히 구현 시)
발생하지 않음
반올림 제어
제한적
직접 구현 필요
세밀한 제어 가능
❓ 이 코드의 결과는?
public class FloatingPointError {
public static void main(String[] args) {
double funds = 0;
for (int i = 0; i < 10; i++)
funds += 0.1;
System.out.println(funds);
System.out.println(funds == 1.0);
}
}
→ 결과는 0.9999999999999999
\n false
👉 왜 이런 결과가 나올까?
부동소수점의 한계: 0.1과 같은 일부 십진수는 컴퓨터에서 정확한 이진수로 표현되지 못해 작은 오차가 발생
오차 누적: 아무리 오차가 작더라도 10번의 덧셈 과정에서 누적되어 최종 결과가 1.0이 아닌
0.999999...
로 계산됨
💡 부동소수점의 한계
float와 double은 IEEE 754 부동소수점 방식을 사용
2진수로 실수를 표현하기 때문에 0.1과 같은 십진수를 정확히 표현할 수 없음
근사값으로 계산되어 금융 계산 등에서 오차 발생 가능
걸프전 당시 미군의 패트리어트 미사일 시스템이 미사일 요격에 실패
원인은 0.1초를 이진 부동소수점으로 정확히 표현하지 못한 오차
시스템 가동 시간이 길어질수록 누적된 오차가 커졌고, 결과적으로 스커드의 위치 예측이 약 500m 이상 빗나가 요격 실패
🚨 잘못된 금융 계산 예시
❌ float/double을 사용한 금액 계산
/**
* double을 사용한 금융 계산의 문제점
* 정확한 값이 계산되지 않아 오류 발생
*/
public class BrokenCashRegister {
public static void main(String[] args) {
double price = 2.10; // 상품 가격
double payment = 1.05; // 지불한 금액
// 거스름돈 계산 - 문제 발생!
System.out.println("받아야 할 금액: " + (price - payment)); // 1.0499999999999998
// 1.05가 나와야 하지만 부동소수점 오차로 인해 다른 값이 출력됨
}
}
✅ BigDecimal을 사용한 정확한 계산
import java.math.BigDecimal;
/**
* BigDecimal을 사용한 정확한 금융 계산
* 정확한 값을 계산할 수 있으나 코드가 장황해짐
*/
public class AccurateCashRegister {
public static void main(String[] args) {
// String 생성자를 사용해 정확한 값 표현
BigDecimal price = new BigDecimal("2.10");
BigDecimal payment = new BigDecimal("1.05");
// 정확한 거스름돈 계산
System.out.println("받아야 할 금액: " + price.subtract(payment)); // 정확히 1.05 출력
}
}
⚠️ BigDecimal
사용 시 주의: double 값으로 BigDecimal을 생성하면 이미 오차가 있는 값이 전달됨 ▶️ String으로 생성
// 잘못된 방법 - 이미 오차가 있는 double 값으로 생성
BigDecimal wrongWay = new BigDecimal(0.1); // 0.1000000000000000055511151231257827021181583404541015625
// 올바른 방법 - 문자열로 생성하여 정확한 값 표현
BigDecimal rightWay = new BigDecimal("0.1"); // 정확히 0.1
💼 BigDecimal의 장단점
👍 장점
정확한 실수 계산이 가능해, 금융 계산 등에 적합함
반올림, 올림, 내림 등을 RoundingMode를 통해 세밀하게 제어할 수 있음
정밀도(precision) 를 자유롭게 설정 가능하여 오차 없는 계산 가능
👎 단점
+
,-
,*
,/
같은 기본 연산자 사용 불가 →add()
,subtract()
등 메서드로만 연산 가능기본 자료형보다 성능이 느림 (약 10배 정도 느리다고 알려져 있음)
일반적으로 기본 자료형에 비해 더 많은 메모리 사용량이 필요하고, 정밀도를 직접 수정할 수 있어 정밀도가 높으면 메모리 사용량이 많이 증가함
🔧 활용 예시: 할인율 계산
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* BigDecimal을 활용한 실전 할인율 계산 예시
*/
public class DiscountCalculator {
public static void main(String[] args) {
// 상품 가격과 할인율
BigDecimal price = new BigDecimal("19.99");
BigDecimal discountRate = new BigDecimal("0.15"); // 15% 할인
// 할인액 계산 (가격 × 할인율)
BigDecimal discountAmount = price.multiply(discountRate);
// 최종 가격 계산 (가격 - 할인액)
BigDecimal finalPrice = price.subtract(discountAmount);
// 소수점 둘째 자리까지 반올림 (HALF_UP: 5 이상이면 올림)
finalPrice = finalPrice.setScale(2, RoundingMode.HALF_UP);
System.out.println("상품 원가: $" + price);
System.out.println("할인액: $" + discountAmount);
System.out.println("최종 가격: $" + finalPrice);
}
}
🔄 int/long을 사용한 방법
🔢 단위 변환 접근법
금액을 계산할 때 달러나 원화 같은 단위 대신 센트나 전 단위로 계산하는 방법
/**
* 주식 거래 시뮬레이션
* 주식 가격을 센트 단위로 계산
* 출력 시에만 실수로 변환, 계산은 모두 정수로 수행
*/
public class StockTrader {
public static void main(String[] args) {
// 센트 단위로 계산
long stockPriceInCents = 12345; // $123.45
int sharesCount = 100;
// 총 비용 계산
long totalCostInCents = stockPriceInCents * sharesCount;
// 결과 출력 (센트 → 달러 변환)
System.out.println("주당 가격: $" + stockPriceInCents / 100.0);
System.out.println("총 주식 수: " + sharesCount + "주");
System.out.println("총 비용: $" + totalCostInCents / 100.0);
// int 사용 시 주의: 오버플로우 가능성 검사
int maxIntValue = Integer.MAX_VALUE; // 2,147,483,647
System.out.println("int 최대값: " + maxIntValue);
System.out.println("큰 금액은 int 범위를 초과할 수 있어 long 사용 권장");
}
}
📊 성능 비교
/**
* BigDecimal과 long의 성능 비교
*/
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
public class PerformanceComparison {
public static void main(String[] args) {
int iterations = 10_000_000;
// long을 사용한 계산 시간 측정
Instant start1 = Instant.now();
long sum1 = 0;
for (int i = 0; i < iterations; i++) {
long value = i * 100; // 달러 → 센트 변환
sum1 += value;
}
Instant end1 = Instant.now();
// BigDecimal을 사용한 계산 시간 측정
Instant start2 = Instant.now();
BigDecimal sum2 = BigDecimal.ZERO;
BigDecimal multiplier = BigDecimal.valueOf(100);
for (int i = 0; i < iterations; i++) {
BigDecimal value = BigDecimal.valueOf(i).multiply(multiplier);
sum2 = sum2.add(value);
}
Instant end2 = Instant.now();
// 소요 시간 비교
long longDuration = Duration.between(start1, end1).toMillis();
long bigDecimalDuration = Duration.between(start2, end2).toMillis();
System.out.println("long 연산 소요 시간: " + longDuration + "ms");
System.out.println("BigDecimal 연산 소요 시간: " + bigDecimalDuration + "ms");
System.out.println("BigDecimal은 long보다 약 " +
(bigDecimalDuration / longDuration) + "배 느림");
}
}
long 연산 소요 시간: 18ms
BigDecimal 연산 소요 시간: 423ms
BigDecimal은 long보다 약 23배 느림
[Execution complete with exit code 0]
🎯 언제 어떤 타입을 사용할까?
🧠 선택 가이드
금융 계산
BigDecimal
정확한 계산이 필수적인 경우
고성능 필요
int/long
성능이 중요하고 소수점 관리가 가능할 때
9자리 이하의 소수점
int
정확한 계산 + 성능 (overflow 주의)
18자리 이하의 소수점
long
정확한 계산 + 성능 (더 큰 범위)
과학/공학 계산
double
정확도보다 넓은 범위의 근사치 계산이 필요할 때
간단한 근사치 계산
float/double
정확한 값이 필요 없는 일반 계산
🎯 결론
📍 금융 계산 같은 정확한 결과가 필요할 때는 float나 double을 피하라
📍 정확한 답이 필요하면 BigDecimal, int 또는 long을 사용하라
📍 성능, 편의성, 정확성 중 우선순위를 정하고 적합한 타입을 선택하라
📍 BigDecimal은 정확하지만 사용이 불편하고 느림을 인지하라
📍 소수점을 직접 관리할 수 있고 성능이 중요하다면 int나 long을 선택하라
💭 느낀 점
💡 금융 계산에서 왜 BigDecimal이 필수적으로 사용되는지, 그리고 실무에서 얼마나 중요한지 체감하게 됨
💡 프로그래밍 언어의 특성과 자료형의 한계를 이해하고 적절한 상황에 맞는 도구를 선택하는 것이 얼마나 중요한지 다시 한번 생각해 보게 됨
Last updated