아이템 60. 정확한 답이 필요하면 float와 double 은 피하라. - ksw6169/effective-java GitHub Wiki

float과 double은 근사치로 계산하도록 설계되었다.

  • float과 double은 이진 부동소수점 연산에 쓰이며 넓은 범위의 수를 빠르게 정밀한 '근사치'로 계산하도록 설계되었다.
  • 따라서 정확한 결과가 필요할 때는 사용하면 안된다.
  • 특히 float과 double 타입은 금융 관련 계산과는 맞지 않는다. (0.1 혹은 10의 음의 거듭제곱 수(10^-1, 10^-2 등) 를 표현할 수 없기 때문이다.)
  • 다음은 double을 사용해 사탕을 몇 개까지 구입할 수 있는지를 계산하는 예제다. 결과값이 올바르지 않은 것을 확인할 수 있다.
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(itemsBought + "개 구입");
    System.out.println("잔돈(달러): " + funds);
}
// 출력 결과
3개 구입
잔돈(달러): 0.3999999999999999

금융 계산에는 BigDecimal, int 혹은 long을 사용해야 한다.

다음은 앞의 예제에서 double을 BigDecimal로 교체한 예제다. 이 예제는 계산을 정확히 수행한다.

public static void main(String[] args) {
    final BigDecimal TEN_CENTS = new BigDecimal(".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(itemsBought + "개 구입");
    System.out.println("잔돈(달러): " + funds);
}
// 출력 결과
4개 구입
잔돈(달러): 0.00

하지만 BigDecimal 에는 단점이 있다. 바로 기본 타입보다 쓰기가 훨씬 불편하고 훨씬 느리다는 점이다.

BigDecimal의 대안으로 int 혹은 long 타입을 사용할 수도 있다.

  • 앞서 서술한 단점들로 인해 int 혹은 long 타입을 대안으로 사용할 수 있다.
  • int/long 타입을 사용할 경우 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 한다.
  • 다음은 모든 계산을 달러 대신 센트로 수행해서 앞에서의 문제를 해결한 예제다.
public static void main(String[] args) {
    int itemsBought = 0;
    int funds = 100;

    for (int price = 10; funds >= price; price += 10) {
        funds -= price;
        itemsBought++;
    }
    
    System.out.println(itemsBought + "개 구입");
    System.out.println("잔돈(달러): " + funds);
}

핵심 정리

  • 정확한 답이 필요한 계산에는 float이나 double을 피하라.
  • 코딩 시의 불편함이나 성능 저하를 신경쓰지 않겠다면 BigDecimal을 사용하라.
  • BigDecimal이 제공하는 여덟 가지 반올림 모드를 이용하면 반올림을 완벽히 제어할 수 있다.
  • 반면 성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int나 long을 사용하라.
  • 숫자를 9자리 10진수로 표현할 수 있다면 int를 사용하고, 18자리 십진수로 표현할 수 있다면 long을 사용하라. 18자리를 넘어가면 BigDecimal을 사용해야 한다.

참고 자료

  • Effective Java 3/E