저번에 부동 소수점에 대한 이야기를 했었습니다. 그러면 C언어에서 double과 float의 차이는 무엇일까요? 정밀도 차이입니다. 그리고 오차 범위도 차이가 날 수 밖에 없는데요. 저는 이 두 가지를 중점적으로 보도록 하겠습니다. 일단, 부동 소수점 같은 경우, 소수부가 2^q꼴 실수인 것들만 표현할 수 있어요. 예를 들어 0.5, 1.625와 같은 것들이 그러한 예입니다.

 

 

[관련글]

왜 0.1을 저장하면 오차가 생길까요?

 

 

 이번에는 오차에 대해서 간략하게 짚고 넘어가 보도록 하겠습니다.

 

 


 먼저 float형, 그러니까 단정밀도를 IEEE 754 형식으로 표현하면 아래와 같습니다.

 

 

 이는 실수 z가 (1.xxx)와 2^(yyy-bias)의 곱으로 표현이 될 때, 저래 들어간다는 겁니다. 단정밀도는 이 bias 값이 127입니다. 예를 들어서, 1.625를 표현해야 한다고 해 봅시다. 그러면 xxx에는 .625가 들어갈 겁니다. 1.625는 1.625에 2^0을 곱한 값으로 표현이 되는데요.

 

 이 때 yyy - bias의 값이 0이기 때문에, 실제 yyy에는 0이 아닌, bias 값인 127이 들어갑니다.

 

 

 이런 식으로 표현을 할 수 있어요. 그러면 오차라는 건 또 무엇일까요?

 

 


 오차는 참값과 근사값의 차이를 의미합니다. 예를 들어서, 참값이 0.5라고 해 봅시다. 그런데, 어떻게 측정을 했는데, 근사값이 0.45라고 해 봅시다. 그러면 오차는 얼마나 되나요? 0.05입니다. 근사값이 0.53이면요? -0.03입니다. 그러면, 이 오차값을 참값으로 나눌 수도 있지 않을까요? 즉, 참값 대비 오차값 비율? 이를 상대 오차라고 이야기 합니다.

 

 예를 들어서, 참값이 0.5였는데, 근사값이 0.45라 하면, 상대 오차는 0.05/0.5 = 0.1이 되는 겁니다. 그러면, float의 상대 오차를 어떻게 구하느냐가 문제인데요. 사실 오차가 발생하는 부분은 초록색으로 칠한 부분이에요.

 

 

 그러면, 초록색 부분을 표현하는 최소 단위가 어떻게 되나요? 1/(2^23)입니다. 이를 10진수 표현으로 정규화를 시켜보면, 1.1에 10^(-7)을 곱한 값임을 알 수 있어요. 그러면 가수 부분인 r이, 1/2 + ... + 1/(2^23) + ... 이런 식으로 표현이 된다고 하면, 가수 부분의 오차가 1.1에 10^(-7)을 곱한 값이 되겠구나. 정도는 쉽게 예측하실 수 있어요.

 

 그러면, 어느 범위까지 표현을 할 수 있을까요? 이것도 복잡하지 않아요. sign 비트가 1이면 음수고, 아니면 양수입니다. 일단 sign 비트가 0이라고 하고, 생각을 해 봅시다.

 

 

 일단 sign 비트 뒤에 오는 8개짜리 bit가 0000 0001 으로 표현이 되어야 합니다. 0000 0000으로 표현이 되는 경우 0으로 표현이 되고, 1111 1111로 표현이 되는 경우 inf로 표현이 되는데요. 이 경우를 제외하면, 8개짜리 bit가 0000 0001로 표현이 되어야 최소가 됩니다.

 

 1에서 127을 빼면 -126이 되는데요. 1.xxx xxx가 0으로 표현이 될 때, 최소가 됩니다. 2^(-126)의 값은 1.175e-38이 됩니다. 즉 실수 r의 절댓값 |r|이 1.175e-38보다 작으면 0으로 표현이 됩니다. 최댓값은 1111 1111이 아닌, 1111 1110으로 표현이 되는데요. 254에서 bias 값인 127을 빼면, 2^127입니다.

 

 

 그러면 이 때, 값은 대략 (2-2^(-23))에 2^127을 곱한 값일 건데요. 2 - 2^(-23)을 대략 1.99999988로 근사하고 보면, 3.40282e+38이 나옵니다. 실수 R의 절대값이 3.40282e+38보다 크면, inf로 표현이 된다는 겁니다.

 

 

 이 프로그램의 실행 결과는 inf가 출력이 된다는 것입니다.

 

 


 double은 어떨까요?

 

 

 double형은 IEEE 754 포맷을 따른다면, 이런 식으로 저장이 됩니다. real, 그러니까 실수부 bit가 52개이니까, 아래와 같이 표현을 할 수 있겠어요.

 

 

 그러면 상대 오차는 어떻게 될까요? 최소 단위가 2^(-52)인 것이 보이시나요? 이 값은 2.220e-16인데요. float 형에 비하면, 오차가 꽤나 작다는 것을 알 수 있어요. 14자리 정도까지는 정확하다는 소리가 되겠습니다. 실제로, double형으로 0.1을 저장했을 때와, float형으로 0.1을 저장하고 나서 출력을 할 때 차이가 있는데요.

 

 

 0.1은 부동 소수점으로 정확하게 값을 표현하지 못합니다. u/2^k꼴로 나타내어지지 않기 때문입니다. 아래 프로그램을 실행시켜 봅시다.

 

 

 위의 것은 double, 아래 것은 float입니다. 딱 봐도, 유효 숫자가 차이가 나는 것을 볼 수 있어요. 이것은, 가수부를 표현할 수 있는 크기가 float에 비해서 double형이 크기 때문입니다. 그러면 double형의 범위는 어떻게 될까요? IEEE 754로 표현이 된다면요.

 

 

 일단 최소인 경우, 지수부가 000 0000 0001로 표현이 되어야 할 겁니다. bias가 1023이니까, 1에서 1023을 빼면 -1022가 나옵니다. 2^(-1022)의 값은 1.1125e-308입니다. 즉 어떠한 실수 r의 절댓값 |r|이 1.1125e-308보다 작다면, 이 친구는 +0이나 -0으로 표현이 된다는 겁니다. 이 때에는 지수부가 000 0000 0000으로 표현이 될 것이기 때문입니다.

 

 

 실제로 이 때, 출력 값은 inf입니다.

 

 

 

 그러면, double형이 표현할 수 있는 실수의 최대 절댓값은 얼마일까요? 2 - 1/2^52에다가, 2^(1023)을 곱한 값일 겁니다. 이 값을 계산해 보면 대략 1.7976e+308이 나오는데요. 실수 r의 절댓값 |r|이 1.7976e+308보다 크면 r은 inf로 표현이 됩니다.

 

 정리해 봅시다. float형에 비해서, double형은 지수부를 표현하는 크기가 커졌습니다. 그러면 표현할 수 있는 실수의 범위가 늘어날 거에요. 그리고 실수부를 표현하는 크기 또한 커졌는데요. 그렇기 때문에, 상대 오차 또한 작아진 셈입니다. 이 정도 정리하시면 좋을 듯 싶습니다.