CS지식

부동소수점 오차에 대하여

쿠카이든 2022. 8. 23. 14:22
728x90
부동소수점 오차
  • 코딩을 하다 보면 +, -, *, / 기호를 사용하여 사칙연산을 하게 된다.
  • 이때 정수간의 사칙연산인데 부동소수점이 발생되는 경우가 발생한다.
  • 오늘은 이 현상에 대해서 정리하고 이 글을 읽은 후에는 부동소수점이 발생하지 않을 것이다.

 

실행 결과는 어떻게 될까?
System.out.println(0.1 + 1.1 == 1.2);
System.out.println(0.1 + 1.1);

 

실행 결과
false
1.2000000000000002

 

false일 수도 있다는 생각이 들었다면 부동소수점에 대한 개념이 있는 사람이라 생각된다.

 


컴퓨터는 1과 0으로만 데이터를 표현한다.

정수의 경우 8은 1000 , 10은 1010으로 표현한다.

실수를 표현하는 방법은 정수에 비해 훨씬 복잡하다. 왜냐하면, 컴퓨터는 실수를 정수와 마찬가지로 2진수로만 표현해야 하기 때문이다. 

 

고정 소수점 방식

실수는 정수부와 소수부로 나눌 수 있다.
실수를 표현하는 가장 간단한 방식은 소수부의 자릿수를 미리 정하여, 고정된 자릿수의 소수를 표현하는 것이다.

 

 


이 방식은 정수부와 소수부의 자릿수가 크지 않으므로, 표현할 수 있는 범위가 매우 적다는 단점이 있다.

 

부동 소수점 방식

실수는 보통 정수부와 소수부로 나누지만, 가수부와 지수부로 나누어 표현할 수도 있다.
부동 소수점 방식은 이렇게 하나의 실수를 가수부와 지수부로 나누어 표현하는 방식이다.

 

고정 소수점 방식은 제한된 자릿수로 인해 표현할 수 있는 범위가 매우 작다.
하지만 부동 소수점 방식은 다음 수식을 이용하여 매우 큰 실수까지도 표현할 수 있다.

 

 

예를 들어, 실수 -12.34를 부동 소수점 방식으로 표현해보면 아래와 같다.

 

 

부동 소수점 방식은 고정 소수점 방식보다 표현할 수 있는 값의 범위가 넓지만, 여전히 정밀도의 문제가 있다.

 

부동 소수점 오류 예

0.1을 100번 더하면 10이 나와야 되는데 10이 나오지 않는다.

double sum = 0;
for(int cnt = 0 ; cnt < 100 ; cnt++) {
	sum += 0.1;
}
System.out.println(sum);
실행결과
9.99999999999998

 

중간 정리

실수는 부동 소수점으로 표현한다. 부동 소수점으로 표현 시 표현할 수 있는 값의 범위는 넓어졌지만 근사한 오차가 발생할 수 있다. 해당 프로그램에서 큰 영향을 미칠 수 있다. 간단한 예를 들어보겠다.

1) 금융 프로그램에서 출금 금액 계산 시 오차가 발생하여 정확한 금액이 출금되지 않았다면?

2) 군사 프로그램에서 미사일의 좌표값을 계산한다고 하였을 때 오차가 발생하였다면?


해결방안

JAVA에서는 BigDecimal이라는 클래스가 존재한다. BigDecimal클래스의 메소드를 이용하여 사칙연산을 하게 되면 부동소수점이 발생하지 않는다.

BigDeciaml 객체 생성 시 String으로 형 변환하여 생성해야 한다는 것을 주의하자.

 

JAVA를 예로 들었지만 언어마다 부동소수점 오차를 해결하기 위한 Util들이 존재할 것이다.

BigDecimal val1 = new BigDecimal("1.1");
BigDecimal val2 = new BigDecimal("0.1");

//더하기
System.out.println(val1.add(val2));		//1.2
//빼기
System.out.println(val1.subtract(val2));	//1.0
//곱하기
System.out.println(val1.multiply(val2));	//0.11
//나누기
System.out.println(val1.divide(val2));		//11

 

double beforeSum = 0;
BigDecimal afterSum = new BigDecimal("0");
for(int cnt = 0 ; cnt < 100 ; cnt++) {
	beforeSum += 0.1;
	afterSum = afterSum.add(new BigDecimal("0.1"));
}
System.out.println("beforeSum : " + beforeSum);	//beforeSum : 9.99999999999998
System.out.println("afterSum : " + afterSum);	//afterSum : 10.0

 

출처: https://fvor001.tistory.com/100

728x90