본문 바로가기
BackEnd/Java

BigDecimal 사용 이유

by 규난 2023. 10. 19.
728x90

목차

  1. 서문
  2. float와 double의 문제점
  3. BigDecimal에 대해서
  4. 결론
  5. reference

 

1.  서문

자바에서는 소수점에 대해서 정확한 계산이 필요한 경우 float, double 타입을 사용하지 말고 BigDecimal을 사용하라고 권장하고 있습니다.

oracle java docs

위 사진의 빨간 줄로 표시된 부분을 번역기를 통해서 해석해 보면 통화 같은 정확한 값을 사용해야 하는 경우에는 java.math.BigDecimal을 사용해야 돼!!라고 친절하게 알려주고 있습니다.

그럼 정확한 소수점에 대해서 계산이 필요할 때 float와 double을 사용하지 말아야 하는지 부터 알아보도록 하겠습니다.

 

2.  float와 double의 문제점

먼저 초깃값 1.0을 가지는 float와 double 변수를 선언 후 값에 0.1을 더하는 코드를 작성해 봅시다.

public class FloatingPoint {

    public static void main(String[] args) {
        float floatPointOfFloat = 1.0F;
        System.out.println("=====float=====");
        for (int i = 0; i < 10; i++) {
            floatPointOfFloat += 0.1F;
            System.out.println("add float: " + f);
        }

        double floatPointOfDouble = 1.0D;
        System.out.println("=====double=====");
        for (int i = 0; i < 10; i++) {
            floatPointOfDouble += 0.1;
            System.out.println("add double: " + d);
        }
    }
}

결과가 어떻게 나올까요??

float와 double의 연산 결과

최종 결과가 2.0일 것이라고 기대한 것과 다르게 맨 뒤에 애매한 숫자들이 붙어있는 것을 볼 수가 있습니다.

이렇게 소수점 계산 결과가 정확하지 않기 때문에 소수점을 정확하게 계산해야 하는 경우에는 자바에서는 float와 double 대신 BigDecimal을 사용하라고 권장을 하고 있는 겁니다.

 

왜 이런 결과가 나오게 됐는지에 대한 이유부터 말씀드리면 부동소수점 때문이라고 말씀드릴 수 있습니다.

오라클 자바 문서를 보면 float와 double은 IEEE 754의 표준을 따른다고 정의되어 있습니다.

IEEE 754는 부동소수점을 표현하는 표준이며 부동소수점은 가수부(fraction: 유효 숫자)와 지수부(exponent: 소수점의 위치)를 나눠서 소수를 표현한 것입니다.

  • sign은 부호를 나타내며 0은 +, 1은 -를 의미하고 1bit의 크기를 가지게 됩니다.
  • float는 single-precision으로 지수부는 8bit, 가수부는 23bit의 크기를 가지게 됩니다.
  • double는 double-precision으로 지수부는 11bit, 가수부는 52bit의 크기를 가지게 됩니다.

IEEE 754의 부동소수점을 표현하는 표준 배치도

컴퓨터는 이진법을 사용해서 모든 데이터를 표현하게 되는데 이렇게 이진법으로 소수를 표현했을 때 발생하는 문제점은 딱 떨어지지 않는 무한 소수들에 대해서 정확한 소수를 표현하지 못한다는 것입니다.

 

부동소수점에 문제점을 가장 잘 보여줄 수 있는 0.1을 예를 들어보도록 하겠습니다.

밑의 코드는 0.1의 값을 가지는 float와 doble을 binary 형태로 변형하는 코드와 실행 결과입니다.

public class FloatingPoint {

    public static void main(String[] args) {
    	String floatToBinaryString = String.format("%32s", Integer.toBinaryString(Float.floatToIntBits(0.1F))).replace(" ", "0");
        System.out.println("float result: " + floatToBinaryString);

        String doubleToBinaryString = String.format("%64s", Long.toBinaryString(Double.doubleToLongBits(0.1))).replace(" ", "0");
        System.out.println("double result : " + doubleToBinaryString);
    }
}

실행 결과

소수 부분은 1/2 + 1/2^2 + 1/2^3 + .... + 1/2^n 을 합쳐서 표현하기 때문에 소수를 정확히 표현하지 못하는 경우가 발생하게 되는 겁니다.

 

3.  BigDecimal에 대해서

그럼 BigDecimal은 소수 부분을 어떻게 계산하길래 자바에서 권장하는 걸까요?

오라클 공식 문서에서는 불변성을 가지며 부호가 있는 임의 정밀도 십진수라고 소개하고 있습니다.

임의 정밀도(arbitrary precision)은 고정된 비트수나 바이트 수로 숫자를 표현하는 대신, 원하는 정밀도나 자릿수로 숫자를 표현하는 방법을 의미합니다.

 

BigDecimal은 임의의 정밀도인 정수 값(uncaled value)과 32bit로 구성된 소수점 오른쪽에 있는 자릿수를 나타내는 sacle 값으로 구성이 되어 있습니다. 예를 들어 1234.123을 BigDecimal로 표현하게 되면 unscaled value는 1234123이고 sacle은 3이 됩니다.

 

이제 위에서 했던 예제와 동일한 값을 BigDecimal을 사용해서 처리해보도록 하겠습니다.

import java.math.BigDecimal;

public class FloatingPoint {

    public static void main(String[] args) {
        BigDecimal bigDecimal = new BigDecimal("1.0");
        BigDecimal addValue = new BigDecimal("0.1");
        for (int i = 0; i < 10; i++) {
            bigDecimal = bigDecimal.add(addValue);
            System.out.println("add BigDecimal: " + bigDecimal);
        }
    }
}

결과가 어떻게 나왔을까요??

float와 double와 다르게 정확한 값이 나오는 것을 볼 수 있습니다.

BigDecimal로 소수 더하기 연산 결과

이렇게 소수점에 대한 결과가 정확하기 때문에 돈과 관련된 계산을 해야 될 경우에는 반드시 BigDecimal을 사용해야 합니다.

BigDecimal은 연산하는 방법과 소수점에 대한 처리 방법 등은 너무 많은 기능들을 제공하고 있기 때문에 이번 포스트에서 전부 다 다루는 것은 주제에 벗어나는 것 같아서 다음에 기회가 된다면 다루어 보도록 하겠습니다.

 

글을 마치기 전에 한 가지 더 말씀드리면 BigDecimal을 사용 시 주의해야 될 사항이 있습니다.

BigDecimal의 인스턴스를 생성할 때는 꼭 String을 매개변수로 받는 생성자로 인스턴스를 생성해야 정확한 값을 얻을 수 있습니다.

 

밑의 사진의 코드를 보시면 double 타입을 매개변수로 받는 생성자로 BigDecimal의 인스턴스를 생성하고 더하기 연산을 처리한 결과입니다.

위 예제에서 String을 매개변수로 받는 생성자로 인스턴스를 생성했을 때와 전혀 다른 결과가 나오는 것을 볼 수 있습니다.

이런 결과가 나온 이유는 addValue에 0.1은 정확한 0.1이 아니기 때문입니다.

(저는 IDE로 intellij를 사용하고 있는데 노란색으로 표시된 부분에 마우스를 가져다 대면 친절하게 String을 매개변수로 받는 생성자로 생성하라고 알려주고 있네요)

BigDecimal을 잘못된 방법으로 사용한 예제

 

4.  결론

소수점 계산 시 결과가 정확하지 않아도 된다 ->  float와 double 사용

소수점 계산 시 결과가 정확해야 한다. -> BigDecimal 사용

저는 여태까지 소수점이 포함되어 있으면 무조건 BigDecimal을 사용했는데 왜 소수점 계산에 대한 결과가 정확해야 할 때  BigDecimal을 사용해야 하는지 이제야 정확하게 알았네요... 부끄럽습니다 ㅋ 더 노력해야겠네요 크크

 

5.  reference

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3

https://en.wikipedia.org/wiki/Single-precision_floating-point_format

https://en.wikipedia.org/wiki/Double-precision_floating-point_forma

https://www.binaryconvert.com/

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigDecimal.html

728x90