본문 바로가기
Java

[Java] 0.1 + 0.2 = 0.3이 아니다!

by 가드 2022. 11. 11.
728x90
@Test
public void doubleTest() {
	Double a = 0.1;
	Double b = 0.2;
	Double c = 0.3;
	Assert.assertEquals(a + b, c, 0.0);
}

java.lang.AssertionError: 
Expected : 0.3
Actual   : 0.30000000000000004

Double Type인 a 변수값은 0.1, b 변수값은 0.2를 대입하고 a + b를 더했다. 0.1 + 0.2 = 0.3은 당연한 결과이다. 하지만 프로그래밍에서는 당연하지 않는 결과가 나오게 된다. 0.3의 결과를 원하지만 실제로는 0.30000000000000004의 값이 나왔기 때문이다.

어떻게 된걸까?

 

간단하게 설명부터 하자면 0.1을 이진법으로 변환하게 되면 0.000110011............로 무한 소수로 정확하게 떨어지지 않게 된다. 컴퓨터의 메모리는 무한하지 않으므로 범위 내에서 가장 근접한 값으로 반올림으로 처리하기 때문에 발생되는 이유이다.

 

java 기준으로 설명하자면 java에서 사용중인 실수 타입은 Float, Double 타입이 있는데 실수 타입은 소수를 표현할 때 IEEE 754 부동 소수점 방식으로 표현하게 되는데 일단 고정 소수점 방식과 부동 소수점 방식을 비교해보자.

고정 소수점 방식

말그대로 정수와 소수의 자릿수를 고장해놓고 실수를 표현하는 가장 간단한 방식이다. 하지만 이 방식은 자릿수 고정으로 인해 표현 할 수 있는 숫자의 범위가 매우 제한된다는 단점이 있다. 

 

부동 소수점 방식 (IEEE 754)

IEEE 754 표준에서 32bit(float)와 63bit(double) 지수부와 가수부로 표현하는 방식을 정해놓고 사용하게 되는데 수에 대한 표현범위가 넓은 대신에 값이 정확하게 떨어지지 않고 근사치 값이 주어지게 된다.

0.1을 부동 소수점으로 표현해보자.

먼저, 0.1를 2진법으로 바꾸면 0.0001100110011001100110011001100110011001100110011001101(2)다.

양수이므로 부호값은 0이다.

0.0001100110011001100110011001100110011001100110011001101(2) = 1.100110011001100110011001100110011001100110011001101(2) × 2-4 로도 표현 가능한데, 이것을 정규화된 부동소수점 수라고 한다.

지수는 -4인데 64bit의 Bias인 1023을 더해서 표기하면 1019이 된다.
1019를 2진법으로 표기시 01111111011(2)가 된다.

가수부는 1.100110011001100110011001100110011001100110011001101의 소수부분으로, 크기는 52bit다. 모자란 뒷 부분은 0을 채운다.

 

표현 설명이 쉽지 않다;;;

 

아무튼 0.1과 0.2 모두 반올림 때문에 0.3 보다 조금 큰 실수(0.300.....4)가 나오게 되는 것이다. 

 

Java에서는 정확한 소수 값을 얻기 위해서는 BigDecimal을 사용해야 한다.

@Test
public void doubleTest2() {
	Double a = 0.1;
	Double b = 0.2;
	Double c = 0.3;
	BigDecimal ad = new BigDecimal(String.valueOf(a));
	BigDecimal bd = new BigDecimal(String.valueOf(b));
	Assert.assertEquals(c, ad.add(bd).doubleValue(), 0.0);
}

BigDecimal은 자바에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 객체이므로 중요한 숫자 연산에서 소수점을 다룬다면 반드시 사용해야 한다. 하지만 연산시 속도가 느리다는 단점은 있다.

300x250

댓글