Goal
- 실수를 표현하는 방법에 대한 이해
- 실수 비교를 할 수 있다
실수(real number) : 수학에서 실직선 위에 점이나, 십진수의 나열로 표현한 수
컴퓨터에서 실수를 표현하기 위해서 다음과 같은 상황들이 고려 되어야 한다.
- 정수 부분과 소수 부분의 표현
- 두 실수 사이에는 무한한 다른 실수가 존재하기 때문에 적절한 근사치로 표현
우선 십진수의 실수 표현에 대해 알아보자
십진수 13.625 는 십진법(위치값 기수법 중 하나)으로 표현하면 다음과 같이 표현 될 수 있다.
$13.625_{(10)} = 1 \times 10^1 + 3 \times 10^0 + 6 \times 10^{-1} + 2 \times 10^{-2} + 5 \times 10^{-3}$
그렇다면 십진수로 표현된 실수를 이진수로는 어떻게 표현될까?
십진수 실수의 정수 부분에 대해서는 정수 표현 방법 과 동일하게 계산하면 된다. (2로 나눈 나머지의 역순으로 계산)
=> 13.125에서 13은 $1101_{(2)}$ 로 표현될 수 있다.
소수 부분도 정수 표현법과 동일하게 계산 하면 될까?
소수 부분 0.625에 대해서 위와 동일한 방식으로 적용하면 다음과 같은 이진수로 표시될 수 있다.
$0.625_{(10)} =0.1001110001_{(2)}$
이는 이진법으로 표기했을 때 0.625가 아닌 이상한 값을 갖게 된다.
그래서 다른 방법으로 계산 되어야 한다. 소수점을 계산 하는 방법은 다음과 같다.
자리 올림수 : 곱 연산에 의한 결과 값에서 정수 부분을 뜻한다. (0 또는 1값을 갖는다)
- 소수 부분이 0이면 종료
- 소수 부분이 0이 아니면 소수 * 2를 한다.
- 2의 결과 값에서 정수 부분을 출력 값으로 그대로 사용한다.
- 2의 결과 값에서 소수 부분은 다음 연산의 값으로 사용하고 위 과정을 반복한다.
쉽게 이해하면 그냥 2를 계속 곱해서 정수 부분을 그대로 출력하는 것이다. (역순으로 출력할 필요가 없다)
십진수 -> 이진수로의 변환 $0.625_{(10)} =0.101_{(2)} = 1 \times 2^{-1} + 0 \times 2^{-2} + 1 \times 2^{-3}$
자, 이로써 소수 부분을 표현할 수 있게 됐다.
하지만 아직 문제가 있다. 실수는 무한하다는 것이다.
한정된 컴퓨터 비트수로는 무한한 실수를 표현할 수 없다.
그래서 컴퓨터는 표현할 수 없는 수에 대해서는 근사한 값으로 표현한다.
실수 표현법
다음은 실수를 표현하는 표현법이다.
- 고정 소수점 표기법(fixed point notation)
- 부동 소수점 표기법(floating point notation)
*여기서 '부동'은 '고정되지 않고 움직인다는 의미로', 흔히 사용하는 '움직이지 않음'의 의미가 아니다.
둘의 가장 큰 차이는 소수점이 움직일 수 있는지 없는지이다.
고정 소수점 표기법(fixed point notation)
소수점이 고정되어 있는 실수 표현법
32비트 실수를 고정 소수점 방식으로 표현하면 다음과 같다.
고정 소수점은 부호, 정수부, 소수부를 표현하는 3부분으로 나뉘고 이 크기가 고정 되어있다.
이런 이유로 아주 큰수나 아주 작은 수를 표현하기에는 적합하지 않다. => 실수 표현 범위가 넓지 않다.
부동 소수점 표기법(floating point notation)
소수점의 위치가 변하는 실수 표기법
컴퓨터에서는 사용하는 부동 소수점 표기법은 IEEE 754 표현법을 따르며,
다음과 같은 형태로 표현된다.
- 부호 비트(singed bit) : 최상위 1bit로 0(양수) 1(음수)의 값을 갖는다.
- 지수(exponent) : 수의 표현 범위를 나타내며, 지수에 의해서 소수점이 이동한다.
- 가수(significand, fraction, mantissa) : 양의 정수로 나타낸 근사치를 나타내며, 정밀도(precison)에 따라 다르다.
이를 수식으로 표현하면 다음과 같다.
$(-1)^s \times m \times b^e$
- s(signed) : 부호
- m(mantissa) : 가수
- b(base / radix) : 밑, 또는 기수 (IEEE 754에서의 2와 10이 밑이 될 수 있다)
- e(exponent) : 지수
ex) 십진수에 대한 부동 소수점 표기 예
176000 을 부동 소수점으로 표기해라
- $1.76 \times 10^{5}$ => 소수점을 오른쪽으로 5칸 움직여서 표현
- $17.6 \times 10^{4}$ => 소수점을 오른쪽으로 4칸 움직여서 표현
- $176000000 \times 10^{-3}$ => 소수점을 왼쪽으로 3칸 움직여서 표현
ex) 이진수에 대한 부동 소수점 표기 예
101.101을 부동 소수점으로 표기해라
- $1.01101 \times 2^{2}$ => 소수점을 오른쪽으로 2칸 움직여서 표현
- $10.1101 \times 2^{1}$ => 소수점을 오른쪽으로 1칸 움직여서 표현
- $101101 \times 2^{-3}$ => 소수점을 왼쪽으로 3칸 움직여서 표현
=> 지수값에 소수점이 이동하면서 큰 값과 작은 값을 표현할 수 있게 됨
하지만 위와 같이 표기하면 혼동이 있다. 같은 수를 서로 다른 부동 소수로 표기가 가능하다.
그래서 정규화된 표기법(normalized notation)을 사용한다.
(실수의)정규화(normalization) : 가수의 첫째 자리가 밑수보다 작은 한자리 자연수가 되도록 바꾸는 것을 정규화
ex) -0.4를 10진수로 2진수로 각각 정규화해라
1) -4를 10진수로 정규화
$-4 \times 10^{-1}$ => 10보다 작은 한자리 자연수( 4 < 10 )로 표현
(가수 부분이 40이나 0.4와 같이 표시된 부동 소수점은 정규화를 거치지 않은 부동 소수점 표기이다)
2) -4를 2진수로 정규화
$-1.6 \times 2^{-2}$ => 4는 2보다 큰 한자리 자연수이므로 가수부에 4가 올 수 없다. 그렇기 때문에 2진수에서 정규화를 거치면 가수부 첫째 수는 항상 1이 된다.
(만약 가수부가 0이라면?? 이 경우는 정규화 자체를 거치지 않는다, 모든 비트가 0으로 표시된다.)
2진수의 정규화 IEEE754표기법
$±(1.xxx) \times 2^{(exponent-bias)}$
1.xxxxx는 가수부를 의미한다.
정규화를 거치면 정수부가 항상 1이 되는데, 이는 계산시 생략해서 표현하므로 hidden bit라고도 한다.
실수의 정밀도 (IEEE754 기준)
단정도 : 32-bit 실수 표현법
- 부호부 : 1bit
- 지수부 : 8bit
- 가수부 : 23bit
- 유효 숫자 : 6자리
배정도 : 64-bit 실수 표현법
- 부호부 : 1bit
- 지수부 : 11bit
- 가수 : 52bit
- 유효 숫자 : 15자리
정규화는 다음 과정을 거쳐서 진행된다.
- 실수의 부호를 부호bit로 표현한다. (0 은 +, 1은 -)
- 절대값을 이진법으로 표현한다. (정수 부분, 소수 부분을 그대로 표현)
- 정규화(normalize) 처리를 하여 산술표현 형식으로 만든다. $m \times b^e$ (m:가수, b:밑, e:지수)
- 위의 결과에서 가수(m)의 가장 앞의 1을 제외한 나머지를 가수부 bit에 채운다. (남는 bit는 0으로 채움)
- 지수(e)는 bias값을 더한 후 이진법으로 표현하여 지수부 bit에 채운다.
*bias : 지수값을 계산 하기 위한 조정값 (32bit 부동 소수점 표현법에서는 127값을 갖는다)
사용이유)
지수부는 양수/음수 모두 가능하기 때문에 이를 표현하는 특별한 방법을 사용하는데 IEEE 754에서는 bias 표현을 사용하여 양수와 음수를 표현한다.
단정도(32-bit) 형식에서 지수부는 8 bit로 구성되어있고 최소값은 -126이고 최대값은 127을 갖는다.
=> bias + e(지수) = 지수부(expoenent). -126 <= e <= 127
음수 : -126 ~ 0(127개), 양수 1~127(127) => 양수, 음수 모두 정확히 반을 사용할 수 있다.
expoenent 가 0인 경우와 255인 경우는 특수하게 처리 되므로 위와 같은 범위 값을 갖는다
참고) IEEE754표기법에서 특별하게 처리 되는 값들 (NaN, 무한대, 0)
다음은 일반적인 정규화 과정을 거치지 특별한 경우이다.
1) NaN (Not a number)
NaN 표현 방법
- 지수부 : 모든 bit가 1이다.
- 가수부 : 0아 아닌 값으로 채워져 있다 (NaN이 발생한 여러 경우를 구분하기 위한 수)
- 부호부 : 의미를 두지 않는다.
2) 무한대 (overflow 발생했을 때에 대한 처리)
무한대 표현 방법
- 지수부 : 모든 bit가 1이다.
- 가수부 : 모든 bit가 0이다.
- 부호부 : 음/양 무한대를 표시 하기 위한 구분자로 사용
3) Signed Zero
IEEE 754에서는 부호가 있는 0을 구분하고 있다.
단 if (x == 0)과 같은 작업에서 불확실한 결과를 낼 수 있기 때문에 -0 = +0 이라는 규칙을 정해놓았다.
부호 있는 0을 사용하게 되면 1 / -∞ < 1 / +∞ 와 같은 표현이 가능하게 된다.
Signed Zero 표현 방법 (부호 비트 뺴고 다 0으로 표현)
- 지수부 : 모든 bit가 0이다.
- 가수부 : 모든 bit가 0이다.
- 부호부 : 음/양 무한대를 표시 하기 위한 구분자로 사용
(C++에서 -0.0을 int형에 메모리복사를 하면 최대 음수값을 나타내고, 대입하면 0으로 표시된다. 신기하다!!)
실수 비교하기
부동 소수점 표기 방식 특성상 정확한 실수값을 표현할 수 없다.
예를 들어, [IEEE 754] 0.1 + 0.2 = 0.30000000000000004이다. 즉, 0.1 + 0.2 ≠ 0.3 이다.
따라서 두 실수값을 비교하기 위해서는 '같다', '다르다' 와 같은 비교가 아니라 '같다고 봐도 무방하다.'와 같은 방식으로 비교를 해야 한다. 이때 사용하는 비교 값이 epsilon이다.
epsilon : 같다고 판단할 만큼 작은 오차 범위를 표현할 때 사용하는 값
두 실수를 비교할 때, 같다고 판단할 오차값으로 사용된다.
epsilon을 사용하면 다음과 같은 실수 비교 함수를 만들 수 있다.
1. 절대 오차 범위 사용하기
구현 코드
bool absoluteEqual(double a, double b)
{
return fabs(a-b) < 1e-10; // 여기에서는 1e-10이 epsilon으로 사용됐다.
}
위와 같은 방법으로 실수가 같은지 비교할 수 있지만, 안전한 방법은 아니다.
그 이유는 다음과 같다.
- a, b의 값이 커짐에 따라 반올림되는 값 또한 커진다. (따라서 허용 오차 또한 커져야 한다)
그래서 다음과 같은 방법 사용할 수 있다.
2. 상대 오차 범위 사용하기
relativeEqual(a,b) = | a - b | / max( | a | , | b | )
=> 실질적인 오차 허용 범위는 a, b가 커질수록 커진다.
구현 코드
bool relativeEqual(double a, double b)
{
// a와 b의 오차가 더 큰 수의 0.000001% 이하이면 true를 반환
return fabs(a-b) <= 1e-8 * max(fabs(a), fabs(b));
}
위 방법으로 상대적인 오차 허용 범위를 구할 수 있게 됐다.
하지만 위 방법 또한 안전하지 않다. 큰 수를 비교할 때는 상관 없겠지만, 매우 작은 숫자들을 비교할 때 문제가 된다.
ex) a = 0, b = 0.00000000000001 인경우 relativeEqual(0,b) = b/b = 1
그래서 절대 오차와 상대 오차를 혼용해서 사용하는 다음과 같은 방법이 사용될 수 있다.
3. 절대 오차 범위, 상대 오차 범위 혼용해서 사용하기
구현 코드
// 절대 오차와 상대 오차를 모두 이용해서 두 수가 같은지 판정
bool doubleEqual(double a, double b)
{
double diff = fabs(a-b);
if(diff < 1e-10) return true; // 절대 오차 허용 범위 안에서는 절대 오차 사용
return diff <= 1e-8 * max(fabs(a) + fabs(b)); // 이외에는 상대 오차 사용
}
[Reference]
[IEEE 754] floating-point에대하여 - 부동 소수점 표현에 대해 자세히 정리해 놓음
프로그래밍 대회에서 배우는 문제해결전략
'컴퓨터 기본 지식' 카테고리의 다른 글
비트 연산에 대한 이해 (0) | 2020.06.24 |
---|---|
컴퓨터의 정수 표현 방법 (0) | 2020.06.24 |
컴퓨터의 수 표현 (0) | 2020.06.24 |
문자 인코딩에 대한 이해 (0) | 2020.06.23 |
스트림, 버퍼에 대한 이해 (0) | 2020.06.22 |