부동 소수점과 분수
2008/07/28 02:14컴퓨터에서는 부동 소수점 연산에 대해, 정보의 신뢰성을 보장할 수 없다. 이는 매우 작거나 큰 수에 대한 더하기, 빼기, 곱하기, 나누기에 대해 적용되는데, 특히 나눗셈에 대해서는 간단한 나눗셈이더라도 정보가 손실될 가능성이 항상 존재한다.
예를 들면, 1을 3으로 나누면, 0.3333333333이 되는데, 이를 다시 3으로 곱하면, 0.9999999999가 된다. 1이 아니다. 이 때문에 수학이라는 학문에서는 분수를 사용하지만, 컴퓨터에서는 대부분 분수를 지원하지 않고 실수로 계산을 한다. 즉, 1을 3으로 나누고 다시 3을 곱하면, 0.9999999999가 나온다. 하지만, 규칙적으로 반복되는 일부 순환 소수에 대해서, 몇몇 언어 또는 몇몇 컴파일러, 라이브러리들은 소수점 마지막 자리에서 반올림을 해주기도 한다. 실제로, Visual C++, gcc, g++ 등의 컴파일러들은 1을 3으로 나누고, 다시 3을 곱하는 결과에서, printf()를 찍어주면, 1.0의 결과를 얻는다.
하지만!!
double f = 3.0;
if ((1.0 / f) * 3.0 == 1.0)
printf("true!\n");
위와 같은 예에서, 비교연산의 결과는 false일 수 있다. (컴파일러가 최적화를 해줬다면, true일 수도 있다.). C/C++은 이와 같은 문제들이 있기 때문에, 공식적으로 명시하기를, 부동소수점에 대해서 == 연산을 하지 말라고 되어있다. 즉, 실제로는 0.999999999로 저장이 되는데, 출력 부분에서 1.0으로 바꿔치기를 하는 것이라고 추론해볼 수 있다.
이런.. 이런 이야길 하려던게 아니고.
이렇게, 순환 소수를 처리해주는 환경도 있는가 하면, 불친절(또는, 정직하게)하게, 0.999999999의 결과를 던져주는 Erlang과 같은 언어도 있다. (사실, 얼랭에서 삽질을 한 이 부동소수점 때문에, 이 글을 쓰게 되었다.)
왜 분수가 아닌, 소수로 계산을 하는걸까? 숫자 계산을 분수가 아닌, 소수로 처리하는 관례는, GUI가 없던 환경에서 온 산물이 아닐까 싶다. 물론, 분수를 표현하기에는 시간적/공간적 비용이 너무 많이 들기도 했고. 하지만, 지금은 2008년이 아닌가. 여기에서 조금만 더 생각을 해보자.
오래된 언어들은 성능 때문에, 여러가지 디자인 이슈들에서 프로그래머의 신경을 충분히 갉아먹었다. The C++ Programming Laugage를 보더라도, "이 부분을 이렇게 했으면 좀 더 직관적이고 좋았겠지만, C++이 만들어진 시대의 특성상, 성능을 위해 디자인을 포기할 수 밖에 없었다."라는 비야네 스트로스트럽 할아버지의 표현이 자주 언급된다. 프로그램을 작성하면서, 부동 소수점을 쓸 때에는 항상 조심해야되는 것이 너무나 당연시 되고 있는데, 사실은 이거, 당연하지 않은 것이 아닐까? 조엘 아저씨의 표현을 빌리자면, 추상화가 잘못 된 것이 아닌가? 이 잘못된 추상화 때문에, 우리는 얼마나 많은 에너지를 소모하고 있는가? 숫자의 계산을 실수로 처리하는 것은, 그렇게 직관적이도 않고, 딱히 당연할 것도 없다. 추상화가 잘못된 주 이유가 성능이지만은, 요즘에는 Big Integer도 내장타입으로 된 언어들이 대부분인 것 처럼, 분수를 primitive 타입으로 갖는 언어가 하나쯤 있어도 전혀 이상할 이유가 없는 시대이다. 분모와 분자가 무한대의 자릿수를 지원하는 분수. 이것 하나만 있으면, 모든 수를 표현할 수 있지 않은가? 원한다면, 출력할 경우에만 소수로 변환하는 메소드가 있어도 되겠다. 물론 분수 자체로도 출력이 되어야 할테고. 음~ 그러면, 1/3 이렇게 찍어줘야 할테니, 나눗셈 기호가 바뀌어야 할테고.. 그리고 또, 뭐가 변해야 할까? 또, 뭐가 필요할까? 분수를 지원하기 위한 언어에는 어떤 기능이 필요할까? 어떤 연산자가 필요할까? 이 언어는 native로 컴파일 될 수 있을까? 질문들이 계속 꼬리에 꼬리를 문다.
-- Jong10

