본문 바로가기

ASP

[삽질방지] Round 함수의 버그??

아래의 글은 버그가 아니였습니다. 제가 모르고 있었네요.

asp에서 round 함수는 반올림 함수가 아님을 알게되었습니다. round함수는 통계학 함수입니다.

예를들어 소수점 첫째짜리까지 있는 여러개의 숫자를 가지고 있고 이 숫자들을 소수점을 제거한 상태에서 평균값을 찾고자 한다면

소수점 이하를 올림, 버림으로 할 경우 값의 오차가 심하게 날 것이고

반올림으로 한다면 5라는 숫자로 끝나는 데이터는 올림이 되어 실제 평균보다 높은 값이 됩니다.

그래서 round라는 함수는 앞의 숫자가 홀수냐 짝수냐에 따라 값을 버리기도 하고 올리기도 하는것입니다.

이렇게 함으로써 확률적으로 실제 평균값에 가깝도록 만드는 것입니다.

그런데 이런 방식을 금융권에서도 쓰고 있다고 하는군요. 이러한 방식을 Banker's Rounding 방식이라고 한다는군요.

대부분의 언어에서는 이러한 방식을 사용하는듯 합니다. (아래 언급한 MSSQL 포함)

그런데 엑셀에서는 우리가 일반적으로 사용하는 반올림이 되는군요.

그리고 닷넷의 Round 함수를 이용할때 MidpointRounding.AwayFromZero 파라미터의 여부에 따라 다른 값을 가질 수 있습니다.

* 우리가 흔히 알고 있는 반올림방식 : Math.Round(2.5, MidpointRounding.AwayFromZero) 
* Banker's Rounding 방식             : Math.Round(2.5) 

결론적으로 asp에서 우리가 일반적으로 알고 있는 반올림을 하고 싶을 경우 아래에 있는 해결방법을 참고하시면 됩니다. ^^
===============================================================================================================================

어떤 프로젝트를 하다가 우연히 발견하게 된 버그(?)입니다.

혹시 이런 경험을 하신 분들이 있으실것 같아 정리해 봅니다.



문제점

ASP에서 Round 함수는 반올림을 해 주는 역할을 하는 함수입니다. 그런데 약간의 버그성이 있습니다.

이것이 Round함수의 버그라기 보다는 제 생각에는 ASP(또는 Windows 시스템)에서 소수점이하 숫자를 표현하는 방식의
문제라고 생각되어집니다. (제가 잘못 알고 있는것이라면 바로잡아 주시기 바랍니다.)

그럼 예제를 통해서 알아보도록 하지요. 아래와 같이 코딩 했을 경우 결과가 어떻게 나올까요? (소수점 제일 끝의 5, 6을 잘 구분해서 보세요.)

<%
  Response.Write Round(1002.15 ,1) & "<br>"
  Response.Write Round(1002.145 ,2) & "<br>"
  Response.Write Round(1002.1445 ,3) & "<br>"
  Response.Write Round(1002.14445 ,4) & "<br><br>"

  Response.Write Round(1002.16 ,1) & "<br>"
  Response.Write Round(1002.146 ,2) & "<br>"
  Response.Write Round(1002.1446 ,3) & "<br>"
  Response.Write Round(1002.14446 ,4) & "<br>"
%>

우리가 기대한 결과는 당연히 아래과 같을 것입니다.

1002.2
1002.15
1002.145
1002.1445

1002.2
1002.15
1002.145
1002.1445

하지만 실제로 실행을 해 보면 아주 다른 결과가 나옵니다. ㅡㅡ;

1002.2
1002.14
1002.144
1002.1444

1002.2
1002.15
1002.145
1002.1445

어찌 이런일이..... 

이런 결과나 나오는것은 위에 말씀드린것과 같이 제 생각에는 숫자표현 방식에 문제가 있어서 일것이란 추측입니다.

예를들면
1002.145 는 내부적으로 1002.14444444444444444444449 와 같이 관리되는듯 합니다. 
1002.146 은 내부적으로 1002.14555555555555555555559 와 같이 관리되는듯 합니다.
(내부적으로 소수 몇째짜리까지 관리하는지는 잘 모르겠습니다. MSSQL2000에서도 이러한 방식을 사용하는것처럼 보입니다.)

그래서 소수 2번째에서 반올림을 하게 되면 1002.144 에서 반올림을 하므로 결과값은 1002.14가 되는것이고
1002.145에서 반올림하므로 1002.15가 되는것입니다.

그런데 희안한것은 소수 첫째짜리에서 반올림할때는 제대로 계산이 되어진다는것입니다. 참 이해가 안됩니다. ㅠㅠ



해결방법

자, 그럼 문제만 알고 있으면 될까요? 아니지요. 해결방법을 찾아야겠죠? ^^

이런 버그성문제를 피해가는 꼼수를 알아봅시다. ASP의 또다른 함수 FormatNumber함수를 이용해 보도록 하겠습니다.

<%
  Response.Write FormatNumber(1002.15 ,1) & "<br>"
  Response.Write FormatNumber(1002.145 ,2) & "<br>"
  Response.Write FormatNumber(1002.1445 ,3) & "<br>"
  Response.Write FormatNumber(1002.14445 ,4) & "<br><br>"

  Response.Write FormatNumber(1002.16 ,1) & "<br>"
  Response.Write FormatNumber(1002.146 ,2) & "<br>"
  Response.Write FormatNumber(1002.1446 ,3) & "<br>"
  Response.Write FormatNumber(1002.14446 ,4) & "<br>"
%>

결과는 어떨까요? 아래와 같습니다.

1,002.2
1,002.15
1,002.145
1,002.1445

1,002.2
1,002.15
1,002.145
1,002.1445

오오오~~ 반올림이 잘 되어있습니다. 원래 FormatNumber 함수는 숫자의 표현형식을 지정하는 함수인데 반올림도 되어집니다.

그런데 여기서 또하나의 문제점!! 바로 천단위마다 콤마(,)가 찍힌다는 문제입니다. 내부적으로 계산할 경우에는 오류를 발생시키지요.

그럼 어떻게 하느냐... 소수점이 있는 숫자이므로 CDbl함수를 이용하여 더블형으로 변환을 시켜주면 해결됩니다.

<%
  Response.Write CDbl(FormatNumber(1002.15 ,1)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.145 ,2)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.1445 ,3)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.14445 ,4)) & "<br><br>"

  Response.Write CDbl(FormatNumber(1002.16 ,1)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.146 ,2)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.1446 ,3)) & "<br>"
  Response.Write CDbl(FormatNumber(1002.14446 ,4)) & "<br>"
%>

결과는
1002.2
1002.15
1002.145
1002.1445

1002.2
1002.15
1002.145
1002.1445

짜잔~!! 이제 이렇게 사용하시면 됩니다. ^^



TIP
숫자형 변환 함수중에 정수로 변환하는 함수는 Int와 CInt가 있다는것을 알고 계시죠? 혹시 이 두 함수의 차이점도 알고 계시나요?
Int 함수소수점 첫째자리에서 버림을 하고 CInt 함수소수점 첫째자리에서 반올림을 한다는 차이점이 있습니다.

그래서 테스트해 봤습니다. 역시 예상대로 반올림에 문제가 발생합니다.

<%
  Response.Write CInt(1002.5) & "<br>"
  Response.Write CInt(1002.6) & "<br>"
  Response.Write CInt(1002.47) & "<br>"
  Response.Write Int(1002.9) & "<br>"
%>

위의 코드대로 라면 분명 아래와 같은 결과를 예상할 것입니다.

1003
1003
1002
1002

그러나 결과는 다음과 같습니다.

1002
1003
1002
1002

역시 반올림이 제대로되지 않습니다.

이 부분들 유의하시어 무한삽질에 빠지지 않도록 하시기 바랍니다. ^^

크리에이티브 커먼즈 라이선스
Creative Commons License