[Java] BigDecimal

무1민·2023년 9월 2일
0

java

목록 보기
1/4
post-thumbnail

BigDecimal?

  • BigDecimal은 Java 언어에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 방법
  • 소수점을 저장할 수 있는 가장 크기가 큰 타입인 double은 소수점의 정밀도에 있어 한계가 있어 유실될 수 있다.
  • Java 언어에서 돈과 소수점을 다룬다면 BigDecimal은 선택이 아니라 필수이다.(현재 핀테크 프로젝트를 진행중이라 알아보고 채용했다.)
  • BigDecimal의 유일한 단점은 느린 속도와 기본 타입보다 조금 불편한 사용법뿐이다.

Double은 왜 안써?

  • 소수점 이하의 수를 다룰 때 double 타입은 사칙연산 시 아래와 같이 우리가 기대한 값과 다른 값을 출력한다.
  • double은 내부적으로 수를 저장할 때 이진수의 근사치를 저장하기 때문이다. 저장된 수를 다시 십진수로 표현하면서 아래와 같은 문제가 발생한다.
double a = 10.0000;
double b = 3.0000;

// 기대값: 13
// 실제값: 13.000001999999999
a + b;

// 기대값: 7
// 실제값: 6.999999999999999
a - b;

// 기대값: 30
// 실제값: 30.000013000000997
a * b;

// 기대값: 3.33333...
// 실제값: 3.333332555555814
a / b;

BigDecimal 기본 용어

  • precision : 숫자를 구성하는 전체 자리수라고 생각하면 편하나, 정확하게 풀이하면 왼쪽부터 0이 아닌 수가 시작하는 위치부터 오른쪽부터 0이 아닌 수로 끝나는 위치까지의 총 자리수 (ex: 012345.67890의 precision은 11이 아닌 9) = unscale

  • scale : 전체 소수점 자리 수라고 생각하면 편하나, 정확하게 풀이하면 소수점 첫째 자리부터 오른쪽부터 0이 아닌 수로 끝나는 위치까지의 총 소수점 자리수이다. (ex: 012345.67890의 scale은 4이다. 하지만 0.00, 0.0의 scale은 모두 1이다.) BigDecimal은 32bit의 소수점 크기를 가진다. = fraction

  • DECIMAL128 : 부호와 소수점을 수용하며, 최대 34자리까지 표현 가능한 10진수를 저장할 수 있는 형식
    거의 모든 금액을 수용할 수 있는 크기이다.

BigDecimal 기본 상수

  • float, double 타입과 다르게 BigDecimal 타입은 초기화가 장황하다. 그래서, 자주 쓰는 0, 1, 10은 쓰기 편하게 미리 상수로 정의되어 있다.
// 0
BigDecimal.ZERO

// 1
BigDecimal.ONE

// 10
BigDecimal.TEN

BigDecimal 초기화

  • double 타입으로부터 BigDecimal 타입을 초기화하는 방법으로 가장 안전한 것은 문자열의 형태로 생성자에 전달하여 초기화하는 것이다. double 타입의 값을 그대로 전달할 경우 앞서 사칙연산 결과에서 본 것과 같이 이진수의 근사치를 가지게 되어 예상과 다른 값을 얻을 수 있다.
// double 타입을 그대로 초기화하면 기대값과 다른 값을 가진다.
// 0.01000000000000000020816681711721685132943093776702880859375
new BigDecimal(0.01);

// 문자열로 초기화하면 정상 인식
// 0.01
new BigDecimal("0.01");

// 위와 동일한 결과, double#toString을 이용하여 문자열로 초기화
// 0.01
BigDecimal.valueOf(0.01);

BigDecimal 비교 연산

  • BigDecimal은 기본 타입이 아닌 오브젝트이기 때문에 특히, 동등 비교 연산을 유의해야한다. double 타입 비교 연산인 ==을 쓰면 안된다.
BigDecimal a = new BigDecimal("2.01");
BigDecimal b = new BigDecimal("2.010");

// 객체의 레퍼런스 주소에 대한 비교 연산자로 무의식적으로 값의 비교를 위해 사용하면 오동작
// false
a == b;

// 값의 비교를 위해 사용, 소수점 맨 끝의 0까지 완전히 값이 동일해야 true 반환
// false
a.equals(b);

// 값의 비교를 위해 사용, 소수점 맨 끝의 0을 무시하고 값이 동일하면 0, 적으면 -1, 많으면 1을 반환
// 0
a.compareTo(b);

BigDecimal 사칙 연산

BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");

// 더하기
// 13
a.add(b);

// 빼기
// 7
a.subtract(b);

// 곱하기
// 30
a.multiply(b);

// 나누기
// 3.333333...
// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
a.divide(b);

// 나누기
// 3.333
a.divide(b, 3, RoundingMode.HALF_EVEN);

// 나누기 후 나머지
// 전체 자리수를 34개로 제한
// 1
a.remainder(b, MathContext.DECIMAL128);

// 절대값
// 3
new BigDecimal("-3").abs();

// 두 수 중 최소값
// 3
a.min(b);

// 두 수 중 최대값
// 10
a.max(b);

BigDecimal 소수점 처리

  • RoundingMode.HALF_EVEN은 Java의 기본 반올림 정책으로 금융권에서 사용하는 Bankers Rounding와 동일한 알고리즘이다. 금융권에서는 시스템 개발시 혼란을 막기 위해 요구사항에 반올림 정책을 명확히 명시하여 개발한다.
// 소수점 이하를 절사한다.
// 1
new BigDecimal("1.1234567890").setScale(0, RoundingMode.FLOOR);

// 소수점 이하를 절사하고 1을 증가시킨다.
// 2
new BigDecimal("1.1234567890").setScale(0, RoundingMode.CEILING);
// 음수에서는 소수점 이하만 절사한다.
// -1
new BigDecimal("-1.1234567890").setScale(0, RoundingMode.CEILING);

// 소수점 자리수에서 오른쪽의 0 부분을 제거한 값을 반환한다.
// 0.9999
new BigDecimal("0.99990").stripTrailingZeros();

// 소수점 자리수를 재정의한다.
// 원래 소수점 자리수보다 작은 자리수의 소수점을 설정하면 예외가 발생한다.
// java.lang.ArithmeticException: Rounding necessary
new BigDecimal("0.1234").setScale(3);

// 반올림 정책을 명시하면 예외가 발생하지 않는다.
// 0.123
new BigDecimal("0.1234").setScale(3, RoundingMode.HALF_EVEN);

// 소수점을 남기지 않고 반올림한다.
// 0
new BigDecimal("0.1234").setScale(0, RoundingMode.HALF_EVEN);
// 1
new BigDecimal("0.9876").setScale(0, RoundingMode.HALF_EVEN);

BigDecimal 문자열 변환 출력

  • .setScale()을 사용하여 소수점 자리 수를 제한하면 원본의 소수점 값을 상실해버린다. 문자열로 출력하는 것이 목적이라면 NumberFormat 클래스를 사용하는 것이 적합하다.
NumberFormat format = NumberFormat.getInstance();
format.setMaximumFractionDigits(6);
format.setRoundingMode(RoundingMode.HALF_EVEN);
// 0.123457
format.format(new BigDecimal("0.1234567890"));

MySQL과 BigDecimal

  • MysSQL 또한 Java와 동일한 문제를 가지고 있다. Float, Double 타입에 소수를 가진 수를 저장할 경우 역시 연산의 정확도 문제가 발생한다. MySQL은 BigDecimal 타입에 대응하는 DECIMAL 타입을 제공한다.
foo DECIMAL(5,2) DEFAULT 0.00 NOT NULL
  • DECIMAL 타입 선언 시 괄호 안의 숫자의 의미는 PRECISION, SCALE을 의미한다. (5,2)라면 전체 자리 수는 5, 소수점 이하 자리 수는 2로 선언하겠다는 의미이다.
  • 만약, 지정된 소수 자리 수보다 많은 값을 저장할 경우, 지정된 소수 자리수 이하는 절사(floor)된다.

출처

https://jsonobject.tistory.com/466

profile
야호

0개의 댓글