오늘자 도지코인의 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을 쓰면 이런 문제가 발생 하는 거지?
자료형 | float | double |
---|---|---|
크기 | 4byte | 8byte |
범위 | -3.4*10^38 ~ 3.4*10^38 | -1.7*10^308 ~ 1.7*10^308 |
유효자릿수 | 소수점 7번째 까지 유효 | 소수점 16번째까지 유효 |
테이블에서 보는것 처럼 float이나 doble은 저장되는 크기가 있고 그 크기 이상을 넘어가는 소수점에 있어서는 정확성을 보장하지 못한다.
따라서, 엄밀히 이야기하자면 float이나 double로는 정확한 실수(예를들면 1.1)을 표현할 수 없다. 근사값의 유효 자릿수 까지만 정확하게 표현할뿐...
조금더 깊게 들어가 보자면 컴퓨터에서 소수점을 표현하는 방법은 두가지가 있는데, 고정소수점과 부동소수점이다
쉽게 이야기하자면
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));
}
}
결과는?
(성능차이가 어마어마하다... 하지만, 정확한 값을 구하기 위해서는 어쩔수 없음...)
@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);
}
}
@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);
}
}
@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);
}
}
@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 상수값으로 가지고있음)
}
}
아쉽지만 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보다 작은경우
}
}
}
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을 살펴 보자면
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을 쓰는 의미가 없음