Study/이펙티브 자바 / / 2024. 4. 3. 21:55

[Effective Java] 정확한 답이 필요하다면 float와 double은 피하라

💥 개요

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

정확한 결과가 필요하면 사용하면 안되며, 특히 금융 관련 계산과 맞지않는다.

1.03 - 0.42 는 출력시 0.6100000000000001을 출력한다. 이건 특수한 사례도 아니다.

 

⚠️ 금융 계산에 잘못된 예시

public class Change {
    // 코드 60-1 오류 발생! 금융 계산에 부동소수 타입을 사용했다. (356쪽)
    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);
    }
}

이 코드에서 사탕을 계산시 아래와 같이 생각과는 다른 결과가 나온다. 아주 작지만 미세하게 차이가 발생한 것이다.

 

❤️ 금융 계산시 올바른 예시

public class BigDecimalChange {
    // 코드 60-2 BigDecimal을 사용한 해법. 속도가 느리고 쓰기 불편하다. (356쪽)
    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);
    }
}

위 코드는 무겁고 속도도 느리며 사용하기에도 불편하다. 하지만 아래와 같이 정확하게 잔돈이 0달러가 남는다. 

단발성 계산은 상관 없겠지만, 사용하기 불편한 것은 조금 아쉽다. 

 

❓ BigDecimal의 대안

대안으로 int 혹은 long 타입을 쓸 수도 있다. 다만 그런 경우 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 한다. 아래 예시에서는 모든 계산을 달러 대신 센트로 수행하여 이 문제를 해결했다.

public class IntChange {
    // 코드 60-3 정수 타입을 사용한 해법 (357쪽)
    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);
    }
}

 

🔎 더 알아보기

1.BigDecimal 사용법

특이하지만 double 타입보다는 String 타입으로 BigDecimal을 사용하는걸 권장하고 있다.

public static void main(String[] args) {
        BigDecimal bigDecimalString = new BigDecimal("10000.12345");
        System.out.println(bigDecimalString); //10000.12345
        
        BigDecimal bigDecimalDouble = new BigDecimal(10000.12345);
        System.out.println(bigDecimalDouble); //10000.123449999999138526618480682373046875
}

그 이유는 위와 같이 정확한 값을 도출해낼 수 없기 때문이다.

\IDE에서도 문자열로 사용하는것을 권장한다.

 

2.사칙 연산자 사용불가

 

 

[refference]

Effective java 3E

https://velog.io/@sa1341/BigDecimal%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90