문제

다음 문제를 어떻게 풀지 생각해 보세요.


"톰은 자동차부품 가게에 가서 1.10 달러짜리 점화플러그를 구매했습니다. 그런데 지갑에 2 달러짜리 지폐밖에 없어서 2 달러를 내고 계산했습니다. 이때 그가 받을 거스름돈은 얼마일까요?"


이 문제는 다음과 같은 프로그램으로 풀 수 있습니다. 무엇을 출력할까요?


public class Change{
	public static void main(String[] args) {
		System.out.println(2.00 - 1.10);
	}
}


풀이

실행 결과는 0.90 이 아니라, 0.8999999999999999 를 출력합니다.자바는 1.1 을 정확하게 double 로 표현할 수 없어 1.1 과 가장 근접한 double 로 표현합니다. 그리고 이를 2.0 과 연산하므로 0.9 가 아니라 0.8999999999999999 가 나오는 것입니다.


모든 소수가 float 또는 double 로 표현될 수 있는 것은 아닙니다. 일부 독자는 자바 5 이상에서 다음과 같이 printf() 메서드를 활용해서 0.90 을 출력하면 해결할 수 있다고 생각합니다.


// 좋지 않은 해결 방법입니다 - 여전히 이진 부동소수점 연산을 사용하고 있습니다!
System.out.printf("%.2f%n", 2.00 - 1.10);


실행 결과는 0.90 이 나오지만, 이는 반올림되어서 제대로 출력되는 것이지 제대로 계산된 것은 아닙니다. 자바는 float 또는 double 자료형으로 정확한 연산을 수행할 수 없으므로 금융 계산 같은 곳에 사용하지 마십시오.


이진 부동소수점 연산

자바는 '이진 부동소수점 연산' 을 사용합니다. 이는 계산은 빠르지만 미세한 오차가 생깁니다. 물론 오차가 없는 '십진 부동소수점 연산' 도 있지만, 자바 기본 자료형 연산에서 이를 지원하지 않습니다. 파이썬, 루비, 자바스크립트에서도 2.0 - 1.1 의 결과로 0.8999999999999999 를 출력합니다.


int 또는 long 자료형 같은 정수 자료형을 이용하면 이런 문제를 해결할 수 있습니다. 이 방법 사용시에는 사용하려는 숫자가 얼마나 큰지 꼭 확인하기 바랍니다. (정수 연산시 오버플로 발생 때문) 다음 코드는 달러 단위가 아니라 센트 단위로 숫자를 표현했습니다. 이렇게 하면 90 센트라는 정확한 값을 얻을 수 있습니다.


System.out.println((200 - 110) + " cents");


혹은 정확한 십진 연산을 하는 BigDecimal 클래스로도 해결 가능합니다. 참고로 JDB (Java Database Connectivity) 에서 SQL DECIMAL 자료형으로 데이터를 처리할 때도 BigDecimal 클래스를 활용합니다. 오차 없는 우리가 원하는 값을 얻기 위해서는, BigDecimal(double) 생성자를 사용하지 말고, BigDecimal(String) 생성자를 사용해야 합니다. 


import java.math.BigDecimal;
public class Change{
	public static void main(String[] args) {
		System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
	}
}


BigDecimal 클래스는 기본 자료형에 비해 조금 느릴 수 있으므로 소수 계산을 많이 하는 프로그램에서는 주의하세요.


정리를 해보면, 정확한 결과가 필요하다면 절대로 float, double 자료형을 사용하지 마세요. 금융 계산 같은 곳에서는 int, long, BigDecimal 자료형을 사용하세요. 만약 독자가 프로그래밍 언어 설계자라면 소수 연산을 할 때 십진 부동소수점 연산을 선택적으로 할 수 있게 고려해 보세요. 예를 들어 BigDecimal 클래스 같은 것에 연산자 오버로딩을 지원하게 하면 쉽게 연산할 수 있게 됩니다. 또한 코볼과 PL/I 프로그래밍 언어처럼 십진 부동소수점 연산을 하는 기본 자료형을 지원하는 것도 좋은 방법입니다.




(조슈아 블로크, 닐 개프터, 『자바 퍼즐러』, 윤인성 옮김, 한빛미디어 (2014.12.04), p28~30 참고)

by kkikkodev 2017. 7. 3. 20:35