SW개발

실수 연산, 어떻게 하시나요???

박종명 2023. 9. 27. 09:26
728x90

이 글은 제가 과거에 운영했던 사이트인 http://dotnet.mkexdev.net 의 글을 옮겨온 것입니다.
그 전에 운영했었던 사이트(mkex.pe.kr)은 흔적도 없이 사라 졌습니다. 그속의 글들도 모두...
그래서 이 사이트도 사라지기 전에 옮기고 싶은 글을 조금씩 이 블로그로 이동시키려 합니다.
(원본글) http://dotnet.mkexdev.net/Article/Content.aspx?parentCategoryID=2&categoryID=9&ID=530

---

(이 글은 닷넷 프레임워크 기반의 예제 및 개념입니다. 그러나 대부분의 개념은 언어와 무관합니다)

여러분들은 '실수 연산'을 할 일이 있나요?

간혹, 랜덤한 값을 계산하거나, 그래프를 그릴 때, 간혹 실수 연산을 할 일이 있을 겁니다
그러나 이 경우에는 정확한 결과가 나오지 않아도 되는 경우가 많습니다

따라서 질문을 다시 해 보죠
여러분들은 정확한 실수 연산을 할 일이 있나요?

다음의 코드의 실행 결과를 예상해 보세요

double a = 11.22;
double b = 11.10 + 0.12;          

bool result = (a == b); 

result 는'' 일까요, '거짓' 일까요?

인간의 계산(?) 방식대로 하면, 11.10 + 0.12 = 11.22 이기 때문에 a, b 두 값은 같으니까, 결과는 '' 이어야 겠죠

그러나 결과는 '거짓' 입니다

컴퓨터는 인간처럼 계산하지 않는다는 게 원인이죠

이와 관련해서 자세한 내용은, 다음의 제 글들을 확인해 주세요
-> 0.1 을 100 번 더해도 10이 되지 않는다?
-> 실수 데이터의 표현 방식 - 부동소수점 방식
-> +- m * ne

그러면, 위 코드에 의해, 컴퓨터가 계산한 결과를 볼까요?
실수의 부동소수점 표현을 보기 위해 C# 에서 Console.WriteLine 에 형식 문자열(E/e)로 16자리까지 봅시다
double a = 11.22;
double b = 11.10 + 0.12;          

Console.WriteLine("{0:e16}", a);
Console.WriteLine("{0:e16}", b);


어떻습니까?

두 값은 엄연히 다릅니다. 이것이 바로 컴퓨터가 계산한 결과 입니다

위에 제시한 링크의 글들을 보셨다면 이유는 아셧을 겁니다
컴퓨터가 실수를 표현하는 부동소수점 방식은 특정한 식에 의해 최대한의 근사치 만을 표현하기 때문입니다 
---------------------------------------------------------------------------------------------------

참고로, 우리나라 화폐단위는 (간편하게도) 소수점이 없죠.
미국 화폐의 경우 센트 단위가 있어, 화폐 계산 할 때 소수점 연산이 필요한 경우가 있습니다

돈이 걸린 문제는 민감하죠. 그래서 돈 계산에 실수가 사용된다면 정확한 결과를 보장해야 겠지요

그런데, 문제는 실수 연산은 정확하지 않다는 겁니다
더 정확하게 말하자면, 부동소수점 방식의 실수 연산을 정확하지 않다는 거죠

만일, 돈 계산과 같이, 실수 연산이 매우 정확해야 한다면 부동 소수점을 사용하면 안됩니다

대표적인 부동 소수점 데이터타입으로는 float(single) 과 double 이 있죠
즉 실수를 double 로 선언해서 돈 계산을 하면 안된다는 말입니다

가장 간편한(?) 방법은 decimal 데이터 타입을 사용하는 것이죠
msdn의 다음문구를 참고하세요

'Single 및 Double에서는 정확하지 않는 숫자가 Decimal에서는 정확하게 표시되는 경우가 있습니다(예: 0.2, 0.3).
Decimal에서는 부동 소수점에서보다 산술 연산이 더 느리지만 더 정확한 결과가 나타나므로 성능 감소를 감내할
만한 가치가 있습니다'

그리고 중요한 것은 현재 환경에 맞는 최적의 선택이죠.  msdn은 이렇게 권고합니다

'Decimal 데이터 형식은 모든 숫자 형식 중 가장 느린 형식입니다. 데이터 형식을 선택하기 전에 정밀도와 성능 중
무엇이 더 중요한지를 충분히 검토해야 합니다'

따라서, (실수가 포함된) 회계계산에는 언어를 불문하고 decimal 을 많이 사용합니다
(한국 화폐 기준만 사용하시나요? 그렇다면, int64 로도 충분할 것입니다)

앞서 예제 코드를 double 대신 decimal 로 바꿔 봅시다
decimal a = (decimal) 11.22;
decimal b = (decimal)11.10 + (decimal)0.12;          

bool result = (a == b);                     
Console.WriteLine(result.ToString());
            
Console.WriteLine("{0:e16}", a);
Console.WriteLine("{0:e16}", b);

결과입니다



값이 같다는 결과를 보여주죠..



이제, 이 글에서 제가 하고 싶은 본론입니다
decimal 을 사용하지 않고도 실수 연산의 결과가 보장(?)되어야 한다는 경우가 있을 것입니다
음.. 사실 이 표현은 정확하지 않죠. 부동소수점 연산 자체가 이미 정확하지 않은데 어떻게 연산의 결과를
보장하겠습니까?

그러면 표현을 다르게 해 보죠
부동소수점 실수를 사용하고도 허용하는 범위 내에서는 결과가 (오차가 없다는 것을) 보장해야 한다
이러면 말이 되죠. 이러한 것을 내결함성 허용이라 합니다

부동소수점 실수 연산에서 연산의 결과가 차이나는 것은 컴퓨터의 실수 표현에 따른 미세한 오차 때문입니다
즉 이러한 미세한 차이는 허용할 수 있다는 가정입니다. 대부분은 이러한 미세한 차이는 허용가능합니다

잘 생각해 보세요. 0.1%의 오차도 허용하지 않겠다.... 라고 한다면 부동소수점 사용하면 안됩니다
실수 연산이 돈 계산만 아니면 (경우에 따라서는 돈 계산이라 할지라도),
어느 정도의 오차는 허용가능한 경우가 대부분입니다

0.1%의 오차 허용이 수치로 와 닿지 않나요?
그러면 특정 소수점 자리 수 까지만 정확성을 보장하면 되나요?

그렇다면, 부동 소수점으로도 충분히 연산 가능합니다

아래 코드는 0.%의 오차를 허용하는 환경에서의 두 값 비교 입니다
double a = 11.22;
double b = 11.10 + 0.12;

double difference = a * 0.0001; //0.1% 오차는 허용함

bool result = Math.Abs(a - b) <= difference; // true 반환

부동소수점 연산 결과의 오차는 특정한 범위 내에서는 허용가능한 경우가 많습니다
즉 컴퓨터가 부동소수점을 표현하는 방식에시 기인한 오차 범위 수준 정도는 무시해도 된다는 거죠

예를 들어, 소수점 2자리 까지만 사용하는 환경에서
0.0000001 오차 때문에 double 대신 decimal 을 사용한다는 것은 좀 오버 아닐까요???

MS 고객지원 사이트에는 다음과 같은 글이 있습니다
'서로 같은지 확인하기 위해 두 개의 부동 소수점 값을 비교하지는 마십시오. 
반드시" 같아야 하는 두 숫자 사이에도 항상 약간의 차이가 납니다.
그 대신 항상 숫자들이 거의 근사한 값인지 확인하십시오.
즉, 두 수의 차이가 아주 작은지 또는 무의미한지 확인하십시오'

다음의 MS 자료들을 찬찬히 읽어 보시길 권장 드립니다
http://support.microsoft.com/kb/42980/ko
http://support.microsoft.com/kb/125056/ko
http://support.microsoft.com/kb/69333/ko
http://msdn.microsoft.com/ko-kr/library/c151dt3s.aspx