본문 바로가기
Java

[Java] 실수형 연산의 오류 , 0.1 + 0.2 = 0.3이 왜 아닐까? (feat. BigDecimal 사용법)

by IGBR 2022. 3. 22.

서론

 알고리즘 문제를 풀다 보니 double형의 계산을 해아 할 때가 있었는데, 간혹 가다 정말 내 머리로는 오점이 없는데 틀렸다고 나온다. 맞왜틀..

 이 경우에는 대부분 나의 논리적 오류였지만, 찾아보니 double형의 합 연산이 근사치를 표현한다고 찾아서 나중에 실수하지 않기 위해, 또한 궁금증을 제대로 풀기 위해 정리하려고 한다. 

 항상 왜 그런지를 알고 어떻게 그렇게 되는지에 대한 절차를 알아야 기억에 오래 남는다. 무작정 외워선 방대한 프로그래밍 지식을 오래 가지고 갈 수 없다고 생각한다. 


본론

일단 왜 그런지에 앞서 한번 출력을 해보자 한다.

 

다음은 0.1과 0.2를 합한 값을 출력하는 프로그램이다.

import java.math.BigDecimal;
public class DoubleError {
	public static void main(String[] args) {
		double a = 0.1;
		double b = 0.2;
		double c = a+b;
		
		System.out.println("일반 + 연산자만 사용 a + b = " + c);
	}
}

 

출력해보면?

우리가 기대하는 값은 0.3이겠지만 실제로 출력되는 모습은 좀 다르다.

잘 보면 소수점 이하의 끝이 4로 출력되는 것을 볼 수 있다. 

 

??? 뭐지..

 

왜 그럴까? 

 

컴퓨터는 한정된 메모리 안에서 최대한 많은 범위를 표현해줄 수 있는 IEEE 754 부동소수점 표현을 사용한다. 이러한 큰 장점이 있는 반면 큰 단점도 동시에 있는데, 바로 앞서 우리가 본 예제이다.

 아쉽게도 값이 근사치로 제공된다.

 

왜 근사치로 값이 나오는 거지?

 

부동 소수점 표현 방식은 실수를 표현할 때 부호, 지수부, 가수부 총 3가지 영역을 사용한다. 

 예를 들어 100010.101을 IEEE 754 표준에 따라 부동소수점으로 변환한다면 정규화(1.xxx*2^b의 형태로 변환)를 먼저 하게 되는데 

이때 1.00010101 * 2^5 이 되고 , 이 수를 가지고 지수부와 가수부에 표현해주게 된다. 여기서 가수부에 표현을 하게 될 때 무한 소수가 나와서, 데이터의 범위를 넘어가면 해당 값은 날아가게 되면서 오차가 발생한다.

 

다른 소수점 표현 방식 쓰면 되지 않나?

 

대표적으로 고정 소수점 방식이 있긴 하나, 부동 소수점 방식의 표현 범위가 고정소수점 표현방식 보다 상대적으로 큰 값을 표현할 수 있다. ( 표현 범위 : 고정 소수점 표현 방식 < 부동 소수점 표현 방식 )

 

그럼 우린 어떻게 소수를 다뤄야 해?

 

자바의 경우 BigDecimal이라는 클래스가 존재하여 오차 없는 소수끼리의 연산 등을 제공해주고 있다.

아래는 BigDecimal의 간단한 사용법이다.

import java.math.BigDecimal;

public class DoubleError {
	public static void main(String[] args) {
		BigDecimal bdA = new BigDecimal(String.valueOf("0.1"));
		BigDecimal bdB = new BigDecimal(String.valueOf("0.2"));
		
		System.out.println("BigDecimal 사용 a + b= " + bdA.add(bdB));
		System.out.println("BigDecimal 사용 a - b= " + bdA.subtract(bdB));
		System.out.println("BigDecimal 사용 a * b= " + bdA.multiply(bdB));
		System.out.println("BigDecimal 사용 a / b= " + bdA.divide(bdB));
	}
}

출력은 다음과 같이 나온다.

와..ㅎㅎ.. 드디어 우리가 바라는 값들이 나온다.

 

하지만 코드에 조금 수상한 부분이 있는데 바로 BigDecimal의 인자로 0.1과 0.2가 String 타입으로 들어간다는 것이다.

 

왜 BigDecimal은 double 도 float도 아닌 String으로 값을 받을까?

BigDecimal의 인자로 float나 double을 받으면 이 역시 정확하지 않은 값을 다루게 된다. 

 따라서 정확하게, 또 전달될 때 오차가 없는 String 형식으로 값을 받게 된다. 

 


결론

실수형의 연산과 실수형 <-> 정수형 간의 형 변환은 값 오류를 초래할 수 있기에, 정말 정확한 실수형의 연산(돈 계산 등 ex. 암호화폐 거래)이 필요하다면 성능을 조금 손해 보더라도 BigDecimal을 사용하자. 

'Java' 카테고리의 다른 글

[Java] Array 와 Array List의 차이 , 공통점  (0) 2022.02.28

댓글