java floating point issue(부동소수점 산술 시 문제점)

부동소수점

부동소수점

코딩을 공부하다보면, 누구나 마주해야할 최악의 적! floating point! 이것은 자바언어의 문제가 아니라 컴퓨터의 자체적인 문제로 실수의 표현 및 계산 시 오차의 원인이 되는 이유이다. 간단히 말해 자바는 실수표현을 부동소수점을 이용하기 때문에 정확한 실수를 저장 할 수 없으며, 최대한 완벽에 가깝기를 바라는 근사치 값을 저장한다.

부동소수점의 정확도 문제(위키)


부동소수점 산술시 문제

1
2
3
4
5
6
7
//예제1: 부동소수점 산술시 문제점
int apple = 1;
double piceUnit = 0.1;
int number = 7;

double result = apple - number * piceUnit; // 원하는 계산: 1 - 7 * 0.1 = 0.3
System.out.println(result); // 결과 : 0.29999999999999993

1 - 7 * 0.1 산술식의 정확한 계산은 0.3이다. 하지만 결과는 0.29999999999999993 이다. 원인은 double이라는 실수형 자료형이 0.1을 정확히 표현하지 못하기 때문이다. 정말 성가신 녀셕이 아닐 수 없다.

따라서 실수의 정확한 계산을 위한 한가지 방법으로는 정수형태로 계산을 한 뒤, 소수점을 나중에 반영하는 것이다.

1
2
3
4
5
6
7
8
//예제2: 부동소수점 산술시 문제점 해결방법
int apple = 1;
int totalPieces = apple * 10;
int number = 7;
int temp = totalPieces - number;

double result = temp / 10.0; // 이런식으로 나중에 소수점 만큼의 계산 결과에 나눠준다.
System.out.println(result); // 결과 : 0.3

만약 자바를 이용해서 계산기 같은 것을 만든다면, 위와 같은 부동수수점 산술시에 실수들을 정수형태로 변형해서 계산하는 것을 고려해볼 필요가 있다.


double과 float의 비교연산시 문제

float는 4바이트 실수, double은 8바이트 실수 값을 저장 할 수 있다. 부동소수점으로 실수를 표현하기 때문에 double은 float보다 상대적으로 보다 더 정확한 실수를 표현 할 수 있다. 문제는 1미만의 값을 갖는 double과 float간의 정확도 차이이다.

1
2
3
4
5
System.out.println(1.0 == 1.0f);  // 결과 : true
System.out.println(1.1 == 1.1f); // 결과 : false
System.out.println(0.1 == 0.1f); // 결과 : false
System.out.println(0.9 == 0.9f); // 결과 : false
System.out.println(0.01 == 0.01f); // 결과 : false

위 예제를 눈으로면 보면 다 true일 것이라 착각 하기 쉽다. 하지만 주석처럼 맨 윗줄만 제외하고 다 false를 출력한다. 이유는 눈에 보이지는 않지만 float와 double자료형의 실수 표현의 정밀도의 차이가 발생하기 때문이다. 따라서 double과 float값을 비교 할때에는 모두 float로 형변환 하거나 정수로 변환하여 비교해야 한다.

1
2
3
System.out.println((float)1.1 == 1.1f);  // 결과 : true
System.out.println(0.1 == (double)0.1f); // 결과 : false
System.out.println(0.1f == (double)0.1f); // 결과 : true

주의 할 점은 0.1f를 double로 형변환 하여 비교해도 될 거라 생각 할 수 있는데, (double)0.1f는 double의 공간에 float의 정밀도를 갖는 값이 저장될 뿐이다. 따라서 double형의 0.1과 비교해도 결과가 true로 나올 수 없다.