Big decimal은 왜 쓰는 거쥬?

probsno·2021년 5월 2일
0
post-custom-banner

1. Big decimal을 쓰는 이유


오늘자 도지코인의 BTC 가격이다. 코인을 모르는 사람에게 조금 설명하고 넘어가자면, 현재가 0.00000652의 의미는 도지코인 1개로 0.00000652개 만큼의 비트코인을 살 수 있다는 의미!

그런데 말입니다?
만약에 업비트 프로그래머가 현재가를 float이나 double로 구현했다면 무슨일이 일어날까? (물논,,, 그럴일은 없겠지만,,,)

public class AppRunner implements ApplicationRunner {
    float dogeCoinPrice = 0.00000652f;
    double quantity = 1000000d;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("당신의 현재 보유액: "+dogeCoinPrice * quantity);
    }
}


결과는 참담하게도 정확하지 않은 금액이 계산되어 나온다. 이와 같이 금액처럼 정확한 소수점을 계산이 필요할 때 float이나 double형을 쓰게 된다면 부정확한 결과가 나오고 정산시에 아주 큰 문제가 될 것임

그렇기 때문에 금액처럼 정확한 소수점을 계산할 때는 반드시 Big Decimal을 사용해야 한다...

그런데,,, 왜 float이나 double을 쓰면 이런 문제가 발생 하는 거지?

1.1 float과 double

자료형floatdouble
크기4byte8byte
범위-3.4*10^38 ~ 3.4*10^38-1.7*10^308 ~ 1.7*10^308
유효자릿수소수점 7번째 까지 유효소수점 16번째까지 유효

테이블에서 보는것 처럼 float이나 doble은 저장되는 크기가 있고 그 크기 이상을 넘어가는 소수점에 있어서는 정확성을 보장하지 못한다.

  • float의 경우 소수점 밑에 7번째 자리까지 유효
  • double의 경우 소수점 밑에 16번째 자리까지 유효
    그렇기 때문에 유효 자리수를 넘어가게 되면 정확하지 않은 값이 나오게 되는 것이었다.

따라서, 엄밀히 이야기하자면 float이나 double로는 정확한 실수(예를들면 1.1)을 표현할 수 없다. 근사값의 유효 자릿수 까지만 정확하게 표현할뿐...

1.2 고정소수점와 부동소수점

조금더 깊게 들어가 보자면 컴퓨터에서 소수점을 표현하는 방법은 두가지가 있는데, 고정소수점과 부동소수점이다
쉽게 이야기하자면

  • 고정소수점 - 특정 비트를 기준으로 정수와 소수점을 구분하는 방법으로 고정적으로 정해놓는 방법이다. (예를들면 4byte의 경우 32bit인데 16bit은 정수, 나머지 16bit는 소수로 고정시킴)

  • 부동소수점 - 고정소수점 방식과 달리 소수점의 위치를 가변적으로 표현할 수 있는방법(소수점이 가변적이기 때문에 떠다딘다고 해서 부동소수점....~_~)

    • 지수부에서 소수점의 위치값을 가지고 있고 가수부에서 숫자를 표현한다
    • 자바의 float, double형 모두 이 부동소수점 방식으로 구현되어 있다.

2. Big decimal 사용하는 방법

Big decimal은 정확한 계산이 가능한 대신에 단점이 있는데

  • 성능상의 손해
  • 약간(?) 더 복잡해진 계산 방법

성능에 대해서 막간 실험! - float 100만번 더하기 vs BigDecimal 100만번 더하기

@Component
public class AppRunner implements ApplicationRunner {

    float floatVal = 1.1f;
    BigDecimal bigDecimalVal = new BigDecimal("1.1");

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Long begin1 = System.currentTimeMillis();
        for(int i=0; i<=1000000; i++){
            floatVal = floatVal + floatVal;
        }
        System.out.println("float 100만번 더하기 계산시간: " + (System.currentTimeMillis() - begin1));

        Long begin2 = System.currentTimeMillis();
        for(int i=0; i<=1000000; i++){
            bigDecimalVal = bigDecimalVal.add(bigDecimalVal);
        }
        System.out.println("BigDecimal 100만번 더하기 계산시간: " + (System.currentTimeMillis() - begin2));
    }
}

결과는?


(성능차이가 어마어마하다... 하지만, 정확한 값을 구하기 위해서는 어쩔수 없음...)

2.1 Big decimal의 연산

  • 더하기 - num1.add(num2);
@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
		num1.add(num2);
    }
}
  • 빼기 - num1.subtract(num2);
@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
		num1.subtract(num2);
    }
}
  • 곱하기 - num1.multiply(num2);
@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
		num1.multiply(num2);
    }
}
  • 나누기 - num1.divide(num2);, num1.divide(num2, 4);, num1.divide(num2, 4, BigDecimal.ROUND_UP);
@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
		num1.devide(num2); //그냥 나누기
		num1.devide(num2, 4); //소수점 4째 자리까지 표시 (2번 파라미터는 소수점 자릿수)
        	num1.devide(num2, 4, BigDecimal.ROUND_UP); //소수점 4째 자리까지 표시 (2번 파라미터는 소수점 자릿수) && 3번째 파라미터는 올림할지 반올림할지 정하는 파라미터(BigDecimal 상수값으로 가지고있음)
    }
}
  • 올림, 내림, 반올림 상수 BigDecimal.XXX는 java9부터 deprecated되었음
  • BigDecimal.XXX 대신에 RoundingMode.XXX 이넘을 쓰도록 합세당~

2.2 Big decimal의 비교

아쉽지만 Big decimal은 비교연산자(<,>,=)로 계산할수 없다. 그렇기 때문에 compareTo() 메소드를 이용해서 비교하는데,,

@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
		if(num1.compareTo(num2) == 0){
            //num1과 num2가 같은 경우
        }

        if(num1.compareTo(num2) >= 1){
            //num1이 num2보다 큰 경우
        }

        if(num1.compareTo(num2) <= -1){
            //num1이 num2보다 작은경우
        }
    }
}
  • 객체보다 파라미터가 더 작으면 1
  • 객체보다 파라미터가 더 크면 -1
  • 객체랑 파라미터가 같으면 0

2.3 Big decimal에서 소수점 처리하는 방법

BigDecimal의 메서드 setScale()은 소수점 몇번째 자리까지 표시할건지 세팅하는 함수임.

@Component
public class AppRunner implements ApplicationRunner {
    
    BigDecimal num1 = new BigDecimal("1");
    BigDecimal num2 = new BigDecimal("2");
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
    	num1.setScale(4);//소수점 4번째 자리까지 표시
        num1.setScale(4, RoundingMode.CEILING);//소수점 4번째 자리까지 표시&& 올림(두번째 파라미터는 enum으로 과거에는 BigDecimal.XXX를 썼지만 deprecated됨)
    }
}

RoundingMode enum을 살펴 보자면

  • RoundingMode.CEILING - 올림 (음수일 경우 내림, 숫자가 더 큰 쪽으로)
  • RoundingMode.UP - 올림 (음수 양수 상관 없이 무조건 올림)
  • RoundingMode.FLOOR - 내림 (음수일 경우 올림, 숫자가 더 작은 쪽으로)
  • RoundingMode.UP - 내림 (음수 양수 상관 없이 무조건 내림)
  • RoundingMode.HALF_DOWN - 반내림
  • RoundingMode.HALF_EVEN - 짝수를 만드는 쪽으로 올리거나 내림
  • RoundingMode.HALF_UP - 반올림
  • RoundingMode.UNNECESSSARY - 사용안함

2.3 추가적으로 Big decimal쓸 때 주의할점은?

Big decimal을 선언할 때 float이나 double로 선언하게 된다면 여전히 big decimal역시 정확하지 않은 값으로 선언되게 되므로 String타입으로 선언하는게 권장된다.

실험을 한번 해봅시당~

@Component
public class AppRunner implements ApplicationRunner {
    BigDecimal initWithFloat = new BigDecimal(1.1f);
    BigDecimal floatToStringBigDecimal = new BigDecimal(String.valueOf(1.1f));
    BigDecimal initWithDouble = new BigDecimal(1.1d);
    BigDecimal initWithString = new BigDecimal("1.1");



    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("new BigDecimal(1.1f): " + initWithFloat);
        System.out.println("new BigDecimal(1.1d): "+ initWithDouble);
        System.out.println("new BigDecimal(stringVal): "+ initWithString);

        System.out.println("new BigDecimal(String.valueOf(1.1f)): "+floatToStringBigDecimal);
    }
}

실험 결과는..

이와 같이 float이나 double로 선언해 버리게 되면 부정확한 값으로 선언되게 되서 big decimal을 쓰는 의미가 없음

  • big decimal을 선언할 때는 String타입으로 선언하거나 float,double타입을 String.valueOf()로 String타입으로 변환후 사용하는 것이 바람직 하다!
profile
3개국어 쌉가능한 주니어 개발자
post-custom-banner

0개의 댓글