본문 바로가기

Reading Record/이펙티브자바

[아이템 59, 60] 라이브러리를 익히고 사용하라, 정확한 답이 필요하다면 float와 double은 피하라

[아이템 59] 라이브러리를 익히고 사용하라

static Random rnd = new Random();

    static int random(int n) {
        return Math.abs(rnd.nextInt()) % n;
    }

무작위 정수 하나를 생성하는 메소드이다. 범위는 0 부터 n 사이의 값이다.

위의 코드는 세 가지의 문제를 가지고 있다.

1. n 이 그리 크지않은 2의 제곱수라면 얼마 지나지 않아 같은 수열이 반복된다.

2. n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다.

3. 지정한 범위 '바깥'의 수가 종종 튀어 나올 수 있다. 

rnd.nextInt() 가 반환한 값을 Math.abs를 이용해 음수가 아닌 정수로 매핑하기 때문이다. nextInt() Integer.MIN_VALUE를 반환하면 Math.absInterger.MIN_VALUE를 반환하고 나머지 연산자가 음수를 반환해버린다.

System.out.println(Math.abs(Integer.MIN_VALUE) % 7);

음수가 나온다.

그럼 어떻게 해야할까?? 해결은 간단하다. Random.nextInt(bound) 를 사용하면 된다. 자바 7부터는 Random 보다 성능이 좋은 ThreadLocalRandom을 사용하면 된다.

그렇다면 라이브러리를 사용하여 얻는 이점으로는 무엇이 있을까?

1. 표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.

2. 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.

3. 따로 노력하지 않아도 성능이 지속해서 개선된다.

4. 기능이 점점 많아진다.
라이브러리에 부족한 부분이 있다면 개발자 커뮤티니에서 이야기가 나오고 논의된 후 다음 릴리즈에 해당 기능이 추가되곤 한다.

5. 우리가 작성한 코드가 많은 사람에게 낯익은 코드가 된다.

자바는 메이저 릴리즈마다 주목할 만한 수많은 기능이 라이브러리에 추가된다. 자바는 메이저 릴리즈마다 새로운 기능을 설명하는 웹페이즈를 공시하는데 한번 쯤 읽어볼 만하니 시간이 될때 읽어보도록 하자.

표준 라이브러리가 매우 방대하기 떄문에 모든 API들을 공부하기 어렵겠지만 적어도 java.lang, java.util, java.io 와 그 하위 패키지들에는 익숙해지는 것이 좋다.

마지막으로 자바의 컬렉션 프레임워크와 스트림 라이브러리, java.util.concurrent의 동시성 기능도 익혀두면 도움이 될 것이다.

 

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

float과 double은 과학과 공학 계산용으로 설계 되었다. 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 근사치로 계산하도록 설계 되었다.

따라서 금융 관련과 같이 정확한 결과가 필요할 때에는 사용하면 안 된다.

예를들어 나한테 1달러가 있고 선반에는 사탕이 10센트, 20센트, 30센트, .... 1달러 까지 있다고 가정해보자. 10센트 부터 순서대로 하나씩 살 수 있을 때까지 사고 사탕을 몇 개 살 수 있고 남은 잔돈을 얼마일까? double을 이용해서 코드로 구현해보자.

public class BuyCandy {
    public static void main(String[] args) {
        double money = 1.00;
        int candyBought = 0;

        for (double price = 0.10; money >= price ; price += 0.10) {
            money -= price;
            candyBought++;
        }

        System.out.println(candyBought + "개 구입");
        System.out.println("잔돈(달러):" + money);
    }
}

잔돈이 굉장해졌다.

금융 계산에서는 BigDecimal, int 또는 long을 사용해야 한다. BigDecimal로 교체해보자.

public class BuyCandy {
    public static void main(String[] args) {
        final BigDecimal TEN_CENTS = new BigDecimal(".10");
        BigDecimal money = new BigDecimal("1.00");
        int candyBought = 0;

        for (BigDecimal price = TEN_CENTS; money.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
            money = money.subtract(price);
            candyBought++;
        }

        System.out.println(candyBought + "개 구입");
        System.out.println("잔돈(달러):" + money);
    }
}

정상 출력

정상적으로 출력되지만 단점이 존재한다. BigDecimal은 기본 타입보다 쓰기가 불편하고 훨씬 느리다.

이번에는 단위를 달러가 아닌 센트로 하고 int 타입을 사용해보자.

public class BuyCandy {
    public static void main(String[] args) {
        int money = 100;
        int candyBought = 0;

        for (int price = 10; money >= price; price += 10) {
            money -= price;
            candyBought++;
        }

        System.out.println(candyBought + "개 구입");
        System.out.println("잔돈(달러):" + money);
    }
}

정상 출력

그러므로 정확한 답이 필요한 계산에는 float이나 double은 사용하지 말자. 

만약 코딩 시의 불편함이나 성능 저하를 신경 쓰지 않겠다면 BigDecimal을 사용하자. BitDecimal은 8가지의 반올림 모드를 제공하기 때문에 반올림을 완벽히 제어 할 수 있다. 법으로 정해진 반올림을 수행해야하는 비즈니스 계산에서는 굉장히 유용한 기능이다.

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