자바에서 소수를 표현할 때 보통 double
이나 float
형식의 자료형을 사용합니다.
자바 관련 강의를 듣는데 강사님께서 이런 말씀을 하시더라구요.
double과 float 대신 BigDecimal 자료형을 사용하세요!
왜 이런 말씀을 하시고 왜 double 과 float 대신 BigDecimal
을 사용해야 하는지 알아봅시다.
부동소수점 연산의 정확도 문제
float 나 double은 부동소수점으로 값을 표현하다 보면, 소수점 이하의 값이 정확하게 표현이 안되는 오류가 발생합니다.
double a = 1.03;
double b = 0.42;
System.out.println(a - b);
// 결과 0.6100000000
위에서 보여지는 코드와 같이 연산이 실행되면, 연산의 기댓값인 0.61이 출력되는게 아닌 예상치 못한 값이 출력됨을 확인할 수 있었습니다.
그 이유는 부동소수점 표현으로 인한 한계 때문인데, 소수 자리수가 굉장히 큰 경우 혹은 무한 소수의 값을 저장하는 경우 적절한 표기를 위해 표준에 맞춰서 근사값의 형태로 값을 표현하기 때문입니다.
부동 소수점 문제가 뭔데?
컴퓨터에서 실수를 표현할 때에는 주로 IEEE 754
표준을 사용합니다. 이 형식을 사용하면 실수를 정확하게 표현하는데 한계가 생기게됩니다.
예를 들어, 0.1이라는 값은 이진수로 정확히 표현할 수 없는 값 중 하나입니다.
왜일까요?
컴퓨터 하드웨어는 0과 1, 두가지 상태만을 이해할 수 있습니다. 따라서 표준에 따라 컴퓨터는 일반적으로 이진수 체계를 사용하는데 0.1은 이진수로 무한히 반복되는 소수로 표현됩니다.
컴퓨터 메모리는 정보를 비트(bit)
단위로 저장합니다. IEEE 754 표준에 따르면, 실수를 컴퓨터 메모리에 저장하기 위해 수를 다음과 같은 세가지 부분으로 나눕니다.
- 부호(sign): 수의 양수 또는 음수 여부를 결정합니다.
- 지수(exponent): 수의 크기를 결정합니다.
- 가수(fraction): 수의 정밀도를 결정합니다.
0.1은 이진수 체계에서 무한히 반복되는 소수로 나타나므로 컴퓨터 메모리에 완벽하게 저장이 불가능합니다. 결과적으로, 저장 공간의 한계로 인해 소수점 이하의 정확한 값을 잃게 되며, 이로 인해 근삿값을 저장하게 됩니다.
0.1(10진수) = 0.00011001100110011...(2진수, 무한 반복)
이 값을 메모리에 저장할 때 가수부에서 사용할 수 있는 비트 수가 제한되어 있기 때문에, 이 무한 반복 소수를 일정 부분까지만 저장하도록 체계가 생기게 된 것입니다.
그래서 자바를 사용할 때는 이러한 문제를 해결하기 위해서 BigDecimal
을 사용하는 것입니다.
BigDecimal
BigDecimal a = new BigDecimal("1.03");
BigDecimal b = new BigDecimal("0.42");
System.out.println(a.subtract(b));
// 결과: 0.61
이렇게 BigDecimal을 사용하면, 아까 위에서 double이나 float를 사용할 때 실패했던 계산이 정확한 결괏값을 반환하게 됩니다.
이것이 가능한 이유는, BigDecimal 자료형의 범위는 double보다 더 넓고, 또 근삿값을 이용한 표현 방식이 아닌, 유효숫자와 소수점을 기준으로 표현하기 때문입니다.
BigDecimal은 애초에 부동소수점의 한계를 극복하기 위해 설계된 자바 라이브러리의 클래스입니다. 이 클래스는 소수점 이하의 정확한 값을 표현할 수 있도록 수를 두 부분으로 나눕니다.
하나는 숫자의 정수 부분을 저장하는데 사용되는 BigInteger
이고, 다른 하나는 소수점 위치를 나타내는 Scale
입니다.
- BigInteger: BigDecimal의 내부 구조에서 BigInteger는 소수점을 포함한 전체 숫자를 하나의 큰 숫자로 표현합니다. 이때 무한 소수같은 계산이 불가능한 수도 정밀한 결괏값을 보장하기 위해서 굉장히 큰 정수까지 저장할 수 있습니다.
- Scale: 스케일은 소수점 이하의 정밀도, 즉 소수점 아래 몇 자리까지 있는지를 나타냅니다.
이러한 설계의 장점을 사용해서 돈을 계산하거나 금융권 프로젝트를 진행할 때는 꼭 소수 표현을 위해 BigDecimal을 사용합니다.
어디가. double, float 버려?
그렇다면 우리가 지금까지 잘 사용해온 double과 float는 앞으로 정말 사용하지 않을까요?
BigDecimal은 불변(immutable) 객체입니다. 즉, 값을 새로 선언할 때 마다 메모리에 저장됩니다.
그래서 메모리를 많이 소모하게 되고, 또 그러한 이유 때문에 연산 속도도 double, float를 사용한 연산에 비해 현저히 떨어지게 됩니다.
따라서 소수를 더 많이 다루거나 정확한 값을 요할 때는 BigDecimal을 사용하고,
메모리를 절약하고 더 빠른 연산이 요할 때는 double, float를 상황에 맞게 사용할 줄 알아야 겠습니다.
'Language > Java ☕️' 카테고리의 다른 글
자바의 static (0) | 2024.05.22 |
---|---|
자바의 메모리 구조 (0) | 2024.05.22 |
자바의 가비지 컬렉션 (0) | 2024.02.02 |
String vs StringBuffer vs StringBuilder (0) | 2024.02.02 |
JDK vs JRE vs JVM (0) | 2024.02.01 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆