ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BigDecimal이란?
    Java 2024. 1. 10. 00:01

    개요

    숫자를 다루는 클래스인 BigDecimal에 대해 들어는 보았지만 사용해 본 적이 없어 이번기회에 사용해 보며 개념을 잡아보고자 합니다.

     

    BigDecimal이란?

    java.math 패키지의 속한 Java 클래스로 정밀한 숫자 연산에 사용됩니다.

    정밀도가 중요한 금융 및 과학 계산에 적합합니다.

    double 및 float와 같은 데이터 유형의 오차를 해결합니다.

     

    BigDecimal이 등장한 이유 (float와 double의 문제점)

    val b: Double = 0.1
    val c: Double = 0.2
    println(b+c)

     

    0.1 + 0.2의 결과는 무엇일까요? 우리는 0.3을 기대할 수 있습니다.

     

    하지만 실제 결과는?

    0.30000000000000004

     

    float와 double은 정확한 값이 아닌 근삿값을 담고 있어 발생하는 부동소수 타입의 대표적인 문제입니다.

    원인을 파악하기 위해서는 고정소수점과 부동소수점에 대한 이해가 필요합니다.

     

    고정소수점과 부동소수점

    컴퓨터에서 실수를 표현하기 위해 고정소수점과 부동소수점 방식을 활용합니다.

     

    고정소수점 표현방식

    https://www.tutorialspoint.com/fixed-point-and-floating-point-number-representations

     

    실수를 부호(sign), 정수부(integer), 소수부(fractional)로 나누어 표현하는 방식입니다.

    자릿수가 제한되어 있어 표현할 수 있는 범위가 한정적입니다.

     

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

    부호 비트인 0은 +입니다.

    정수부 비트인 0000111은 7입니다.

    소수부 비트인 1100000은 75입니다.

    위와 같은방법으로 7.75를 2진수로 표현할 수 있습니다.

     

     

    부동소수점 표현방식

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

    부호, 가수부(Mantissa), 지수부(Exponent)로 나누어 표현하는 방식입니다.

    수식으로는 부호 * M(가수부) * 2^E(지수부)로 표현할 수 있습니다.

     

    예를 들어 12.3456를 저장한다면 0.123456 * 10^2로 변경한 다음 가수부에는 0.123456을 담고 지수부에는 2를 저장하는 방식입니다.

     

    실제로는 지수부에 bias라는 값을 더해지는 과정이 거쳐집니다.

     

    고정소수점 방식보다는 표현범위가 더 넓지만 근본적으로 2진수를 사용하므로 0.3을 2진수로 변환하면 0.0100110011처럼 무한적인 반복이 발생합니다.

     

     

    BigDecimal으로 사칙연산 수행

    val seven = BigDecimal("7")
    val three = BigDecimal("3")
    
    // a.add(b)와 동일
    // 10
    println(seven + three)
    
    // a.subtract(b)와 동일
    // 4
    println(seven - three)
    
    // a.multiply(b)와 동일
    // 21
    println(seven * three)
    
    // a.divide(b, RoundingMode.HALF_EVEN)와 동일
    // 2
    println(seven / three)
    
    // Java와 동일한 방법으로 메서드를 호출할 수도 있다
    // 2.3
    println(seven.divide(three, 1, RoundingMode.HALF_UP))

    Kotlin에서는 연산자 오버로딩을 지원하기 때문에 간편하게 사용할 수 있습니다.

     

    이때 나눗셈의 경우에는 1/3처럼 값이 무한소수인 경우에 ArithmeticException이 발생하기 때문에 소수점 처리 전략을 지정해주어야 합니다.

     

    BigDecimal 주의할 점

    1) double 생성자 대신 String 생성자 이용하기

    val doubleValue: Double = 0.3
    val bigDecimalByDoubleValue = BigDecimal(doubleValue)
    println(bigDecimalByDoubleValue)
    
    val bigDecimalByString = BigDecimal("0.3")
    println(bigDecimalByString)
    
    
    //결과
    //0.299999999999999988897769753748434595763683319091796875
    //0.3

    0.3을 기대했지만 double값을 생성자로 BigDecimal을 만들면 근삿값이 그대로 들어가기 때문에 Stirng 생성자나 valueOf() 메서드를 활용해야 합니다.

     

    2) 나눗셈시 소수점 처리 전략을 설정하기

    val one = BigDecimal("1")
    println(one.divide(three))
    
    //ArithmeticException 예외 발생
    //Caused by: java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

    소수점 처리를 하지 않으면 특정 연산 시 ArithmeticException이 발생할 수 있습니다. 

    Kotlin의 확장함수를 사용하여 소수점 처리를 공통적으로 수행할 수 있습니다.

     

    2-1) Kotlin 확장함수 활용으로 소수점 처리 전략 정하기

    val one = BigDecimal("1")
    println(one.div(three))
    //결과 : 0
    
    operator fun BigDecimal.div(other: BigDecimal): BigDecimal = this.divide(other, HALF_UP_ROUNDING_MODE)
    
    class BigDecimalUtils {
    	companion object {
    		val HALF_UP_ROUNDING_MODE = RoundingMode.HALF_UP
    	}
    }

     

    3) 동등성 비교 시 compareTo() 활용하기

    val zeroWithDecimalPoint = BigDecimal("0.000")
    val zeroBigDecimal = BigDecimal.ZERO
    
    println(zeroWithDecimalPoint == zeroBigDecimal) //결과 : false
    println(zeroBigDecimal.compareTo(zeroBigDecimal)) //결과 : 0

    소수점 이하의 자리까지 체크하는 일을 거의 없기 때문에 compareTo를 활용하는 게 좋을 것 같습니다.

     

     

    마무리

    • 컴퓨터과학에서 부동소수점의 발생할 수 있는 문제점
    • BigDecimal에서는 나눗셈 연산을 수행할 때 전략을 적절하게 잘 활용해야 하는 점
    • Kotlin 연산자 오버로딩과 확장함수의 활용법

    가볍게 튜토리얼을 수행해보려고 했지만 다양한 부분들에서 인사이트를 얻어가는 것 같습니다.

     

     

     

    참고자료

    https://dev.gmarket.com/75

    'Java' 카테고리의 다른 글

    구독의 보너스 날짜 계산하기  (0) 2023.09.19
    Throwable 클래스란?  (0) 2023.06.16
    Instant vs LocalDateTime  (0) 2023.05.27
    [Java] 날짜,시간과 관련된 LocalDateTime의 역사  (0) 2022.08.27
    [Java]ExecutorService란?  (0) 2022.07.31

    댓글

Designed by Tistory.