개발자로 후회없는 삶 살기
자바 PART.무한의 값 처리 BigDecimal 본문
서론
개인적으로 공부하고 있는 우아한 테크코스 로또 문제에서 발생한 소수점 계산 문제를 해결합니다.
본론
- float과 double의 문제점
float와 double은 정확한 값이 아닌 근삿값을 담고 있는 문제가 있습니다.
-> 고정 소수점과 부동 소수점
1) 고정
실수를 부포비트, 정수부, 소수부로 나누고 자릿수를 고정하여 실수를 표현하는 방식입니다.
ex) 7.75 = 111.11(2)
7.75는 실수를 2진수로 변환하면 111.11이 되는데 이를 정수부와 소수부로 나눠서 담아 표현합니다. 구현법이 간단하지만, 자릿수 제한이라는 단점이 있습니다.
2) 부동
실수를 부호부, 가수부, 지수부로 나누는 방식입니다.
ex) 12.3456 = 0.123456 * 10^2
12.3456를 0.123456 * 10^2이처럼 변경하고 가수부에는 0.123456, 지수부에는 2를 저장하는 방식입니다. 표현범위는 넓지만 가수부에 여전히 소수를 사용하여 오차가 발생합니다.
float과 double이 부동 소수점 표현방식으로 구현되었기에 앞선 문제가 발생합니다. 빅 데시멀은 고정 방식의 정수부와 부동 방식의 지수부를 혼합하였습니다.
- 빅 데시멀이란?
임의 정밀도와 부호를 지니는 10진수로 무한에 가까운 수를 배열에 나눠 담는 방식으로 구현됩니다. 빅 데시멀은 내부적으로 임의 정밀도 연산을 활용하는 동시에 불변이므로 객체간의 연산마다 새로운 객체가 생성됩니다. 따라서 float, double와 같은 기본 타입에 비해 느리며, 어렵습니다.
int, Long이라는 대체제가 있지만, 실수를 표현할 수 없고 값의 범위가 비교적 제한된다는 점 때문에 금융 관련 계산에서 빅 데시멀은 필수입니다.
-> 빅 데시멀의 구성
빅 데시멀은 임의 정밀도 정수형인 unscaled value와 소수점 오른쪽 자리수를 나타내는 32비트 정수인 scale로 구성됩니다. ex) 3.14의 경우 unscaled value는 314이고 scale는 2입니다.
intVal : BigInteger 타입으로 임의 정밀도 정수형인 unscaled value를 저장
scale : 소수점 오른쪽 자릿수 표현
percision : 총 자릿수 표현
intCompact : 값이 너무 작을 경우 사용
내부적으로 위 4개로 구성되어 있습니다. 빅 데시멀과 double이 비슷해 보이는 데 차이는 double은 가수부에 소수를 저장하고 있고 빅 데시멀은 intVal에 정수형을 저장해서 크고 디테일한 계산을 정확히 할 수 있습니다.
-> 빅 데시멀의 생성
BigInteger, char[], double, int, long, String 등 다양한 타입을 통해 BigDecimal을 생성할 수 있습니다. 대신 double 형은 근삿값을 담고 있기 때문에 valueOf 정적 팩토리 메서드를 사용해서 생성해야 합니다.
-> 연산
주의할 점은 파이썬의 // 처럼 나누기가 정확한 몫을 반환하기 때문에 소수점 처리 전략을 지정해줘야 합니다.
비교 연산, 소수점 처리 등을 할 수 있습니다.
- 주의점
1) double 시 valueOf
2) 소수점 처리 전략 미 수행시 ArithmeticException
3) 동등성 비교 시 compareTO는 정수만 비교 / equals는 정수와 소수 모두 비교
equals는 모두 비교하니 틀리다고 나오고 compareTo는 정수만 봤을 때 같다고 나옵니다. 소수점 이하의 자릿수까지 체크해야 하는 경우는 거의 없으므로 헷갈린다면 compareTo() 사용을 추천한다고 합니다.
- 우테코 직면한 이슈
double 형으로 매우 큰 로또 값을 연산하려고 하는 과정이 필요했습니다.
그 결과 범위를 넘어버리는 문제가 발생했습니다.
이를 BigDecimal로 바꾸니 매우 큰 범위도 수용할 수 있음을 확인하였습니다.
참고
'[백엔드] > [spring+JPA | 이슈해결]' 카테고리의 다른 글
[최적화] Redis Stream 내부 ObjectHashMapper를 이용하여 HASH 역직렬화 자동화하기 (2) | 2024.11.24 |
---|---|
[최적화] Redis Stream에 적절한 RedisSerializer를 사용하자 (2) | 2024.11.23 |
spring PART.postman으로 login 테스트 할 때 받아온 토큰을 요청 헤더에 자동으로 넣는 방법 (0) | 2023.08.18 |
spring PART.Value Object와 Custom Validator를 이용한 검증 개선 (0) | 2023.07.21 |
[Java] Java의 immutable (0) | 2023.07.07 |