티스토리 뷰

(Effective Java) 규칙 60. 정확한 답이 필요하다면 float과 double은 피하라


float과 double의 설계


  • floatdouble 타입을 과학과 공학 계산용으로 설계되었다.

    • 이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 '근사치'로 계산하도록 세심하게 설계되었다.
    • 따라서 정확한 결과가 필요할 때는 사용하면 안된다.
  • floatdouble 타입은 특히 금융 관련 계산과는 맞지 않는다.

    • 0.1 혹은 10의 음의 거듭 제곱 수(10^-1, 10^-2 등)를 표현할 수 없기 때문임

 

float과 double 사용의 문제


  • 예) 주머니에 1.03달러가 있었는데 그중 42센트를 썼다면 남은 돈은 얼마인가?

    //답을 구하는 어설픈 코드 .. 
    System.out.println(1.03 - 0.42);
    • 안타깝게도 이 코드는 0.6100000000000001을 출력하게 됨 (이는 특수한 사례도 아님)
  • 예) 주머니에 1달러가 있었는데 10센트짜리 사탕9개를 샀다고 했을 때 남은 돈은 얼마인가?

    //답을 구하는 어설픈 코드 .. 
    System.out.println(1.00 - 9 * 0.10);
    • 이 코드는 0.09999999999999998을 출력하게 됨
    • 결괏값을 출력하기 전에 반올림하면 해결되리라 생각할지 모르지만, 틀린 답이 나올 수 있다.
  • 예) 주머니에 1달러가 있고, 선반에 10, 20센트, ... 1달러짜리의 사탕이 놓여 있을 때 사탕을 살수 있는 개수와, 잔돈은 얼마나 될까?

    //오류 발생! 금융 계산에 부동소수 타입을 사용했다. 
    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로 교체만 했다.

    • 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달러가 남게 됨
  • 하지만 BigDecimal에는 기본 타입보다 쓰기가 훨씬 불편하고, 훨씬 느리다는 두 가지 단점이 있음

    • 단발성 계산이라면 느리다는 문제는 무시할 수 있지만, 쓰기 불편하다는 점은 못내 아쉬울 것임
  • BigDecimal의 대안으로 int 혹은 long 타입을 쓸 수도 있다.

    • 그럴 경우 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 함
    • int 방식으로 구현해본 예제
      public static void main(String[] args) {
        final BigDecimal TEN_CENTS = new BigDecimal(".10");
      
        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이 제공하는 여덟가지 반올림 모드를 이용하여 반올림을 완벽히 제어할 수 있음
  • 반면, 성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 intdouble을 사용하라.

    • 숫자를 아홉 자리 십진수로 표현할 수 있다면 int를, 열여덟 자리 십진수로 표현할 수 있다면 long을 사용
    • 이를 넘어가면 BigDecimal을 사용

끝으로

이 글이 도움이 되었다면, 하단의 Google 광고 👎👎👎 한번씩 클릭 부탁 드립니다. 🙏🙏🙏

광고 클릭은 많은 힘이 됩니다! 

반응형
댓글