개발자로 후회없는 삶 살기

자바 PART.무한의 값 처리 BigDecimal 본문

[백엔드]/[spring+JPA | 이슈해결]

자바 PART.무한의 값 처리 BigDecimal

몽이장쥰 2023. 11. 27. 22:04

서론

개인적으로 공부하고 있는 우아한 테크코스 로또 문제에서 발생한 소수점 계산 문제를 해결합니다.

 

본론

- float과 double의 문제점

float와 double은 정확한 값이 아닌 근삿값을 담고 있는 문제가 있습니다.

 

-> 고정 소수점과 부동 소수점

1) 고정

[ https://madplay.github.io/post/the-need-for-bigdecimal-in-java ]

실수를 부포비트, 정수부, 소수부로 나누고 자릿수를 고정하여 실수를 표현하는 방식입니다. 

 

ex) 7.75 = 111.11(2)

7.75는 실수를 2진수로 변환하면 111.11이 되는데 이를 정수부와 소수부로 나눠서 담아 표현합니다. 구현법이 간단하지만, 자릿수 제한이라는 단점이 있습니다.

 

2) 부동

[ [ https://madplay.github.io/post/the-need-for-bigdecimal-in-java ] ]

실수를 부호부, 가수부, 지수부로 나누는 방식입니다.

 

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로 바꾸니 매우 큰 범위도 수용할 수 있음을 확인하였습니다.

 

참고

[빅 데시멀]

 

 

Comments