Floney

[플로니] 가계부 금액 타입 정하기 (double vs decimal)

딤섬뮨 2023. 11. 12. 18:23
728x90

1. 우리 가계부는 999억까지 최대 금액으로 입력이 가능하다.

2. 우리 가계부는 다양한 화폐단위를 지원한다. 따라서, 소수점이 필요하다

이렇게 돈이 많은 사람이 있을까? 부럽다

 

 

거의 막바지에 발견했지만 크리티컬한 이슈가 있었다

 

바로, 화폐 단위를 고려하지 않고 금액 타입을 long으로 설정하는 바람에... 소수점이 불가능해졌다.

따라서 급하게 수정을 해야했다.

 

사실 처음에 아무 생각이 없이... 정밀도를 고려하지 않고 단순히 실수형이니깐 float로 저장을 했다가 재수정이 요해졌다.

float는 정밀도가 7 자리기에, 999억을 저장하게 되면 오차가 발생한다. 정말 기본이 중요함을 여기서 깨닫고 반성 차 글을 써본다

 

1. float

 

float는 4바이트(32bit)까지 표현이 가능하다.

부호(1bit) + 지수(8bit) + 가수(23bit)  = 4byte

정밀도 : 7자리

 

-3.4 * 10^38 ~ 3.4 * 10 ^ 38까지 표현이 가능하다.

 

+ = 부호

3.4 = 가수

38 = 지수

 

이렇게 나누어 저장한다. 그래서 같은 크기의 정수 int보다 더 범위를 많이 표현할 수 있다.  하지만, 나누어 저장하여서 부동 소수점 방식에서 오차가 발생할 수 있다

 

+ 무한소수, 순환소수의 경우 가수부가 표현할 수 있는 비트 수를 넘어가게 되면 손실되는 부분이 생기기 때문, 실수 또한 이진수로 표현하기 때문에 가수부가 1/2^n 꼴로 표현되는 경우만 오차 없이 정확하게 값이 계산된다.

[CS] 부동 소수점 오차 (velog.io)

 

2. double

 

64bit = 8byte를 사용한다.

double도 부동 소수점 방식을 사용하긴 한다. 하지만, 가수의 자리가 float의 2배이기에 정밀도가  15자리로 늘어난다.

 

그렇다면 double은 정답이 될 수 있는가? 물론 우리 앱은 999억이 최대 + 소수점 하위 2자리라 총 12 자리긴해서 요구사항도 만족한다

 

3. decimal

16바이트의 값 범위를 갖는다.(메모리 제일 많이 사용)

decimal은 정확한 십진수 연산을 제공하며, 정밀도 손실 없이 큰 숫자나 소수점 이하 자릿수를 다룰 수 있다.

 

고정 소수점 방식을 사용하기에 정수 부분과 실수 부분을 나누어 표현한다

근데 공수가 많이 들고, 속도가 느리다는 단점이 있다.

 

예전에 이펙티브 자바 스터디를 할 때 기억나는 챕터이다. (아이템 60)

- 책에서는 int, long을 이용하여 실수를 표현할 수 있다고 설명해주기도 한다.

 

 

결론

double vs decimal

어떤 자료형을 써야 할까? 결국 트레이드오프인 것 같다.

우리 앱이 금융 앱이긴 하지만, 단순 가계부 서비스 일뿐 실제 사용자의 자산과 생활에 영향을 주는 것이 아니기에... 속도와 정합성을 놓고 봤을 때, 속도가 더 중요하다고 생각한다.

더욱이 저렇게 큰 금액을 입력하는 사용자보다, 적은 금액(double)으로 표현가능한 금액을 작성하는 사용자가 많기에, 

double 타입으로 작성하는 게 맞다고 생각된다.

 


+ 성능 테스트

+어떤 분이 댓글로, 다음과 같은 의견을 주셔서 보완해보고자 한다.(감사합니다)

 

double 로 123.456 + 789.012 를 더하는 함수와

BigDecimal로 123.456 + 789.012를 더하는 함수를 비교해보았다.

public static void main(String[] args) {
        int iterations = 1000000; // 작업을 반복할 횟수

        // Decimal 성능 테스트
        long decimalStartTime = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            performDecimalOperation();
        }
        long decimalEndTime = System.nanoTime();
        long decimalDuration = decimalEndTime - decimalStartTime;
        System.out.println("Decimal 소요 시간: " + decimalDuration / 1000000 + "ms");

        // Double 성능 테스트
        long doubleStartTime = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            performDoubleOperation();
        }
        long doubleEndTime = System.nanoTime();
        long doubleDuration = doubleEndTime - doubleStartTime;
        System.out.println("Double 소요 시간: " + doubleDuration / 1000000 + "ms");
    }

    private static void performDecimalOperation() {
        // Decimal 작업 수행 예시
        java.math.BigDecimal decimalValue1 = new java.math.BigDecimal("123.456");
        java.math.BigDecimal decimalValue2 = new java.math.BigDecimal("789.012");
        java.math.BigDecimal result = decimalValue1.add(decimalValue2);
    }

    private static void performDoubleOperation() {
        // Double 작업 수행 예시
        double doubleValue1 = 123.456;
        double doubleValue2 = 789.012;
        double result = doubleValue1 + doubleValue2;
    }

 

매번 소요시간은 달라지지만, double보다 Decimal이 훨씬 소요시간이 긴걸 볼 수 있다.

 

 

 

728x90