3장 코딩과 디버깅에 관하여 3
3.6 실수 자료형의 이해
부동 소수점 표기
자료형 | 부호 비트 | 지수 비트 | 가수 비트 | 지수 범위 | 유효자리수(십진수) |
---|---|---|---|---|---|
32비트 실수형 | 1 | 8 | 23 | -27+2 ~ 27-1 | 6 |
64비트 실수형 | 1 | 11 | 52 | -210+2 ~ 210-1 | 15 |
80비트 실수형 | 1 | 15 | 64 | -214+2 ~ 214-1 | 18 |
실수 비교하기
int same = 0;
for(int x = 1; x <= 50; ++x) {
double y = 1.0 / x;
if(x * y == 1.0)
++same;
}
위 코드를 실행해보면 same의 값은 50보다 적다.
실수 1/x가 근사값이 되어서 x와 곱해도 1이 안 되기 때문이다.
이를 보완하기 위해 비교문을 다음과 같이 수정하였다.
bool absoluteEqual(double a, double b) {
return fabs(a - b) < 1e-10;
}
// fabs()는 부동 소수점 인수의 절대값을 계산한다.
int same = 0;
for(int x = 1; x <= 50; ++x) {
double y = 1.0 / x;
if(absoluteEqual(1, x * y))
++same;
}
하지만 수가 굉장히 커져서 1-10의 오차범위를 벗어난다면 수정된 코드 또한 잘못된 결과를 낳을 수 있다.
이를 해결하기 위해 크게 두 가지 방법을 사용한다.
- 입력될 값의 범위를 예측하여 직접 오차 범위를 설정한다.
- 입력될 값의 크기에 비례해서 오차 범위를 설정한다.
비교할 실수의 크기들에 비례한 오차 한도를 정한다.
입력의 최대치와 최소치를 예측할 수 있다면 오차 한도의 상한과 하한을 구해서 적용한다.
상대 오차를 이용한다.
// a와 b의 오차가 더 큰 수의 0.000001% 이하이면 true를 반환한다.
bool relativeEqual(double a, double b) {
return fabs(a - b) <= 1e-8 * max(fabs(a), fabs(b));
}
하지만 이 방법은 max(fabs(1), fabs(1e-12)) = 1
처럼 1-8보다 더 작은 차이가 나는 수들이 같은지 확인 하는 데에는 부적절하다.
그래서 두 수의 절대 오차가 매우 작을 경우에 두 수가 같다고 판단하는 조건문을 추가한다.
// 절대 오차와 상대 오차를 모두 이용해서 두 수가 같은지 판정한다.
bool doubleEqual(double a, double, b) {
double diff = fabs(a) - fabs(b);
// 절대 오차가 허용 범위 안일 경우 무조건 true를 반환한다.
if(diff < 1e-10) return true;
// 이 외의 경우에는 상대 오차를 사용한다.
return diff <= 1e-8 * max(fabs(a), fabs(b));
}
대소 비교
두 수가 같은지 판단하는 것이 아닌 대소 비교에서도 오차로 인한 오답을 피하기 위해서 비교하는 두 값이 아주 가까운 경우를 먼저 확인하고 처리해야 한다.
실수 연산 아예 하지 않기
- 곱셈과 나눗셈의 순서를 바꾸기
- 양변 제곱하기
- 실수 좌표를 써야 하는 기하 문제에서 좌표계를 가로 세로로 정수배 늘리기
Leave a comment