BigDecimal과 함께 - 정확한 숫자 계산을 Java로 구현하기
BigDeciaml With Java
이번 Post는 Java에서 금액을 저장할 때 사용하는 방법을 기록한다. 원시형(primitive) 변수로 저장하는 방법과 Java가 지원하는 Class를 정리한다.
double/float
한국은 소수점 밑으로 금액이 없다. 그래서 int타입 변수로 처리하는 로직을 생각할 것이다. 하지만, 환산과 세율로 인해 소수점 곱셈이 발생하면 int타입으로는 해결할 수 없다. 그러면, 소수점을 지원하는 double과 float을 생각한다. 그런데 double과 float의 사칙연산 결과는 우리가 생각하는 것과 다르다.
@Test public void 원시형_사칙연산() { double a = 0.1; double b = 0.2; double c = 0.3; // double 끼리 사칙연산. System.out.println(a + b - c); // 예상 : 0 / 결과 : 5.551115123125783E-17 System.out.println(a * b * c); // 예상 : 0.006 / 결과 : 0.006000000000000001 float d = 0.1f; float e = 0.2f; float f = 0.3f; // float 끼리 사칙연산 System.out.println(d + e - f); // 예상 : 0 / 결과 : 0.0 System.out.println(d * e * f); // 예상 : 0.006 / 결과 : 0.0060000005 // 섞어서 사칙연산 System.out.println(a + e - f); // 예상 : 0 / 결과 : -8.940696738513054E-9 System.out.println(d * b * f); // 예상 : 0.006 / 결과 : 0.00600000032782555 double g = 11000; double h = 1.15; System.out.println(g*h); // 예상 : 12650.00 / 결과 : 12649.999999999998 }
double과 float은 값을 저장할 때, 이진수 근사치를 저장한다. 이 근사치를 다시 십진수로 변환할 때 생각과 다른 값이 출력된다. 찾아보니 해결방법으로 2가지가 있었다.
소수점까지 고려한 Double과 Float 을 만든다.
내가 표현하려는 금액의 소수점까지 고려해서 정수형처럼 계산 후에 소수점을
지정하는 방법이다.
@Test public void 원시형_대안_사칙연산() { double a = 1; double b = 2; double c = 3; double d = 0.001; System.out.println(a*b*c*d); // 예상 : 0.006 / 결과 : 0.006 }
객체를 이용한 계산을 한다.
많이 사용하는 방법으로 BigDecimal Class가 있다. 거의 무한대의 숫자를 정확한 십진수로 표현할 수 있다.
BigDecimal
생성
파라미터는 double이나 float값이 아닌 문자를 사용한다. double/float이 값으로 입력되어 예상하는 결과와 다르게 나타난다.
@Test public void BigDecima_생성_테스트() { BigDecimal createdByString = new BigDecimal("0.001"); BigDecimal createdByDouble = new BigDecimal(0.001d); System.out.println(createdByString); System.out.println(createdByDouble); }
사칙연산
@Test public void BigDecima_사칙연산_테스트() { BigDecimal a = new BigDecimal("0.01"); BigDecimal b = new BigDecimal("0.02"); BigDecimal c = new BigDecimal("0.03"); // 덧셈 : 0.01 + 0.02 + 0.03 = 0.06 System.out.println(a.add(b).add(c)); // 덧셈 : 0.01 + 0.02 - 0.03 = 0 System.out.println(a.add(b).subtract(c)); // 덧셈 : 0.01 * 0.02 = 0.0002 System.out.println(a.multiply(b)); // 나누셈 : (0.01 + 0.02) / 0.03 = 1 System.out.println(a.add(b).divide(c)); // 나누셈 : 절대값(0.01 - 0.02 - 0.03) = 0.04 System.out.println(a.subtract(b).subtract(c).abs()); // 나누셈 : -(0.01 + 0.02 = 0.03) = -0.06 System.out.println(a.add(b).add(c).negate()); }
반올림, 올림, 내림.
@Test public void RoudingMode_테스트() { // 반올림 System.out.println(new BigDecimal("1.55").setScale(1, RoundingMode.HALF_UP)); // 1.6 // 올림 System.out.println(new BigDecimal("1.51").setScale(1, RoundingMode.UP)); // 1.6 // 버림 System.out.println(new BigDecimal("1.56").setScale(1, RoundingMode.DOWN)); // 1.5 }
비교
비교할 때, equals와 compareTo가 있지만, compareTo를 사용하자. compareTo는 10진수 값으로 비교하지만 equals는 객체로 비교한다.
@Test public void BigDecima_비교_테스트() { BigDecimal a = new BigDecimal("0.01"); BigDecimal b = new BigDecimal("0.02"); BigDecimal c = new BigDecimal("0.03"); System.out.println(a.add(b).subtract(c).compareTo(BigDecimal.ZERO) == 0); // true System.out.println(a.add(b).subtract(c).equals(BigDecimal.ZERO)); // false }
JPA
JPA는 BigDecimal 타입의 컬럼을 지원한다. 선언해서 사용하면 Converter없이 사용가능하다. 다음 Post에서 BigDecimal을 사용한 Application을 만들어 확인해 볼 예정이다.
참고
- https://jsonobject.tistory.com/466
- https://blog.leocat.kr/notes/2019/02/25/java-rounding
댓글
댓글 쓰기