[Effective Java] 48. 정확한 답이 필요하면 float, double은 피하라

1 분 소요

float와 double은 부동소수점 연산을 수행한다.
이는 넓은 범위의 값에 대해서 정확도가 높은 근사치를 제공할 수 있도록 설계된 연산으로 정확한 값을 보장하지 않는다.

특히나 돈과 같이 정확한 연산을 할때는 절때 float와 double을 사용해서는 안된다.

예를 들어 아래 예는 보이지 않는 버그가 있다.

public static void main(String[] ar) {
	double funds = 1.00;
	int itemsBought = 0;
	
	for(double price = 0.10; funds >= price; price += 0.10) {
		funds -= price;
		itemsBought++;
	}
	
	System.out.println("Bought : " + itemsBought);
	System.out.println("Charge : " + funds);
}

위 예는 1달러를 가지고 있는 사람이 10센트, 20센트, 30센트 식으로 10센트씩 증가하는 상품을 1개씩 살때 총 몇개를 살 수 있는지를 계산하는 예이다.

정확한 계산이라면 10센트, 20센트, 30센트, 40센트 이렇게 4개를 사고 나머지는 0원이 되어야 한다.
하지만 실제 결과는 3개를 살 수 있고 나머지는 0.3999999 라고 출력된다.

BigDecimal, int, long 을 사용할 것

돈 계산과 같이 정확한 계산을 하고자 할때는 BigDecimal이나 int, long을 사용해야 한다.

위 버그가 있는 코드를 BigDecimal로 수정하면 아래와 같다.

public static void main(String[] ar) {
	final BigDecimal TEN_CENTS = new BigDecimal("0.10");
	
	int itemsBought = 0;
	BigDecimal funds = new BigDecimal("1.00");
	for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
		funds = funds.subtract(price);
		itemsBought++;
	}
	
	System.out.println("Bought : " + itemsBought);
	System.out.println("Charge : " + funds);
}

위 예제는 예상한대로 정상동작한다.
하지만 BigDecimal의 경우 기본 연산자보다 사용하기에 불편하고 성능상 느리다.

이에 대한 대안은 기본 연산자 int나 long을 사용하는 것이다. 둘 중 어떤것을 사용하느냐는 수의 크기, 소수점 이하 몇자리까지 표시할 것인가에 따라 다르다.

아래 예는 기본 연산자 int로 수정한 것이다. 소수점 연산을 하지 않기 위해 1달러를 100센트로 변환하여 계산한 것이다.

public static void main(String[] ar) {
	int funds = 100;
	int itemsBought = 0;
	
	for(double price = 10; funds >= price; price += 10) {
		funds -= price;
		itemsBought++;
	}
	
	System.out.println("Bought : " + itemsBought);
	System.out.println("Charge : " + funds);
}

만약 위 예와 같이 정확히 나눠 떨어지지 않는다면 소수점을 어디까지 표시하는지에 따라서 다를 것이다.

소수점 이하를 시스템에서 알아서 처리해주기를 바라고 사용하기 조금 불편하고 성능이 좋지 않아도 괜찮다면 BigDecimal을 사용하는 것도 괜찮다.

댓글남기기