본문 바로가기

회고/이펙티브 자바 3판

[ 아이템 60 ] 정확한 답이 필요하다면 float와 double은 피하라

반응형

float와 double 타입은 이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 '근사치'로 계산하도록 섬세하게 설계되었다. 따라서 정확한 결과가 필요할 때는 사용하면 안된다. float와 double 타입은 특히 금융 관련 계산은 맞지 않는다. 0.1, 10의 음의 거듭제곱 수 등은 표현할 수 없다.

 

 

System.out.println(1.03 - 0.42);

결과 : 0.0610000000001

System.out.println(1.00 - 9*0.10);

결과 : 0.09999999999999

 

오류 발생! 금융 계산에 부동소수 타입을 사용했다

public static void main(String[] args) {
	double funds = 1.00;
    int itemsBought = 0;
    for (double price=0.10; funds>=price; price +=0.10) {
    	funds -=price;
        itemsBought++;
    }
    System.out.println(itemBough + "개 구입");
    System.out.println("잔돈(달러):" + funds);
}

프로그램을 실행해보면 사탕 3개를 구입한 후 잔돈은 0.3999999999999달러가 남게 된다. 이 문제를 해결하기 위해서는 금융 계산에는 BigDemical, int 혹은 long을 사용해야 한다.

 

 다음은 double타입을 BigDecimal로 교체만 했다. BigDemical의 생성자 중 문자열을 받는 생성자를 사용했음에 주목한다. 계산시 부정확한 값이 사용되는 걸 막기 위해 필요한 조치다.

public static void main(String[] args) {
	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(itemBough + "개 구입");
    System.out.println("잔돈(달러):" + funds);
}

올바른 답이 나오지만 BigDecimal에는 2가지 단점이 있다.

기본 타입보다 쓰기가 훨씬 불편하고, 훨씬 느리다. 단발서 계산이라면 상관 없지만, 쓰기 불편한건 마찬가지이다.

 

BigDecimal 대안으로 int 혹은 long 타입을 쓸 수도 있다. 그럴 경우 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 한다. 그래서 계산을 달러 대신 센트로 바꾸면 된다.

 

정수 타입을 사용한 해법

public static void main(String[] args) {
	int funds = 100;
    int itemsBought = 0;
    for (int price=10; funds>=price; price +=10) {
    	funds -=price;
        itemsBought++;
    }
    System.out.println(itemBough + "개 구입");
    System.out.println("잔돈(센트):" + funds);
}

성능이 중요하고, 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int나 long을 쓰는 것이 좋다.

int는 숫자 아홉자리를, long은 열여덟 자리를 십진수로 표현할 수 있다. 이 자리수를 넘어가면 BigDecimal을 고려하라

 

*느낀점

알고리즘 문제를 풀면서, float와 double의 문제점을 본 적이 있다. 그 당시, 이런 문제점이 의아했으며 해결책으로는 원하는 소수점 자리 갯수만큼 10의 거듭제곱을 곱하고 다시 10의 거듭제곱을 double형인 100.0 , 1000.0 형식으로 나누는 것이었다. 하지만 일단 이 책에서 추천하는 것은 BigDecimal과 단위를 변경하는 것이므로 해당 방식을 시도해보아야 겠다. 

반응형