부동 소수점 계산의 정밀도 및 정확도
원래 KB 번호: 125056
요약
프로그래머에게 놀라운 결과를 생성하기 위해 부동 소수점 계산의 정밀도, 반올림 및 정확도가 작동할 수 있는 많은 상황이 있습니다. 다음 네 가지 일반적인 규칙을 따라야 합니다.
단일 정밀도와 배정밀도를 모두 포함하는 계산에서 결과는 일반적으로 단일 정밀도보다 더 정확하지 않습니다. 배정밀도가 필요한 경우 상수 등 계산의 모든 용어가 배정밀도로 지정되어야 합니다.
단순 숫자 값이 컴퓨터에 정확하게 표현된다고 가정하지 마세요. 대부분의 부동 소수점 값은 유한한 이진 값으로 정확하게 나타낼 수 없습니다. 예를 들어 이
.1
.0001100110011...
진(영원히 반복됨)이므로 모든 PC를 포함하는 이진 산술 연산을 사용하는 컴퓨터에서 완전한 정확도로 나타낼 수 없습니다.결과가 마지막 소수점까지 정확하다고 가정하지 마세요. "true" 답변과 부동 소수점 처리 단위의 유한 정밀도로 계산할 수 있는 항목 간에는 항상 작은 차이가 있습니다.
두 부동 소수점 값이 같거나 같지 않은지 비교하지 마세요. 이것은 규칙 3에 대한 등록입니다. "같아야"하는 숫자 사이에는 거의 항상 작은 차이가 있을 것입니다. 대신 항상 숫자가 거의 같은지 확인합니다. 즉, 둘 사이의 차이가 작거나 중요하지 않은지 확인합니다.
추가 정보
일반적으로 위에서 설명한 규칙은 C, C++, 어셈블러를 비롯한 모든 언어에 적용됩니다. 아래 샘플에서는 FORTRAN PowerStation을 사용하는 몇 가지 규칙을 보여 줍니다. 모든 샘플은 C로 작성된 마지막 샘플을 제외하고 옵션 없이 FORTRAN PowerStation 32를 사용하여 컴파일되었습니다.
예제 1
첫 번째 샘플은 다음 두 가지를 보여 줍니다.
- 기본적으로 FORTRAN 상수는 단정밀도입니다(C 상수는 기본적으로 배정밀도임).
- 단일 정밀도 용어를 포함하는 계산은 모든 용어가 단일 정밀도인 계산보다 훨씬 정확하지 않습니다.
1.1(단일 정밀도 상수)으로 초기화된 후 y는 단일 정밀도 변수만큼 정확하지 않습니다.
x = 1.100000000000000 y = 1.100000023841858
단일 정밀도 값을 정확한 배정밀도 값으로 곱한 결과는 두 개의 단일 정밀도 값을 곱하는 것만큼 나쁩합니다. 두 계산 모두 두 개의 배정밀도 값을 곱하는 것 만큼 수천 배의 오류가 발생합니다.
true = 1.320000000000000 (multiplying 2 double precision values)
y = 1.320000052452087 (multiplying a double and a single)
z = 1.320000081062318 (multiplying 2 single precision values)
샘플 코드
C Compile options: none
real*8 x,y,z
x = 1.1D0
y = 1.1
print *, 'x =',x, 'y =', y
y = 1.2 * x
z = 1.2 * 1.1
print *, x, y, z
end
예제 2
샘플 2는 이차 방정식을 사용합니다. 이중 정밀도 계산조차도 완벽하지 않으며, 작은 오류가 과감한 결과를 가져올 수 있는지에 따라 달라지기 전에 계산 결과를 테스트해야 한다는 것을 보여 줍니다. 샘플 2의 제곱근 함수에 대한 입력은 약간 음수에 불과하지만 여전히 유효하지 않습니다. 배정밀도 계산에 약간의 오류가 없는 경우 결과는 다음과 같습니다.
Root = -1.1500000000
대신 다음 오류가 생성됩니다.
런타임 오류 M6201: MATH
- sqrt: DOMAIN 오류
샘플 코드
C Compile options: none
real*8 a,b,c,x,y
a=1.0D0
b=2.3D0
c=1.322D0
x = b**2
y = 4*a*c
print *,x,y,x-y
print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
end
샘플 3
샘플 3에서는 최적화가 설정되지 않은 경우에도 발생하는 최적화로 인해 값이 일시적으로 예상보다 높은 정밀도를 유지할 수 있으며 두 부동 소수점 값이 같은지 테스트하는 것은 현명하지 않다는 것을 보여 줍니다.
이 예제에서는 두 값이 같고 같지 않습니다. 첫 번째 IF에서 Z 값은 여전히 보조 프로세서의 스택에 있으며 Y와 동일한 정밀도를 가집니다. 따라서 X는 Y와 같지 않으며 첫 번째 메시지가 출력됩니다. 두 번째 IF 시, Z는 메모리에서 로드되어야 했기 때문에 X와 동일한 정밀도와 값을 가지고 있었고 두 번째 메시지도 인쇄됩니다.
샘플 코드
C Compile options: none
real*8 y
y=27.1024D0
x=27.1024
z=y
if (x.ne.z) then
print *,'X does not equal Z'
end if
if (x.eq.z) then
print *,'X equals Z'
end if
end
샘플 4
샘플 코드 4의 첫 번째 부분은 1.0에 가까운 두 숫자 사이의 가능한 가장 작은 차이를 계산합니다. 1.0의 이진 표현에 단일 비트를 추가하여 이 작업을 수행합니다.
x = 1.00000000000000000 (one bit more than 1.0)
y = 1.00000000000000000 (exactly 1.0)
x-y = .00000000000000022 (smallest possible difference)
일부 버전의 FORTRAN은 표시할 때 숫자를 반올림하여 내재된 숫자 정확도를 명확하지 않도록 합니다. 이것이 x와 y가 표시될 때 동일하게 표시되는 이유입니다.
샘플 코드 4의 두 번째 부분은 10.0에 가까운 두 숫자 사이의 가능한 가장 작은 차이를 계산합니다. 다시 말하지만, 10.0의 이진 표현에 단일 비트를 추가하여 이 작업을 수행합니다. 10에 가까운 숫자 간의 차이는 1에 가까운 차이보다 큽니다. 이는 숫자의 절대값이 클수록 지정된 비트 수에 덜 정확하게 저장할 수 있다는 일반적인 원칙을 보여 줍니다.
x = 10.00000000000000000 (one bit more than 10.0)
y = 10.00000000000000000 (exactly 10.0)
x-y = .00000000000000178
이러한 숫자의 이진 표현도 표시되어 1비트만큼 차이가 있음을 보여 줍니다.
x = 4024000000000001 Hex
y = 4024000000000000 Hex
샘플 코드 4의 마지막 부분에서는 단순 반복되지 않는 소수점 값이 반복 분수로만 이진으로 표현될 수 있는 경우가 많습니다. 이 경우 x=1.05- 반복 인수 CCCCCCCC가 필요합니다.... (16진수) FORTRAN에서 마지막 숫자 "C"는 가능한 가장 높은 정확도를 유지하기 위해 "D"로 반올림됩니다.
x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)
반올림 후에도 결과가 완벽하게 정확하지는 않습니다. 첫 번째 숫자를 제거하여 확인할 수 있는 가장 낮은 유효 자릿수 이후에 몇 가지 오류가 있습니다.
x-1 = .05000000000000004
샘플 코드
C Compile options: none
IMPLICIT real*8 (A-Z)
integer*4 i(2)
real*8 x,y
equivalence (i(1),x)
x=1.
y=x
i(1)=i(1)+1
print "(1x,'x =',F20.17,' y=',f20.17)", x,y
print "(1x,'x-y=',F20.17)", x-y
print *
x=10.
y=x
i(1)=i(1)+1
print "(1x,'x =',F20.17,' y=',f20.17)", x,y
print "(1x,'x-y=',F20.17)", x-y
print *
print "(1x,'x =',Z16,' Hex y=',Z16,' Hex')", x,y
print *
x=1.05D0
print "(1x,'x =',F20.17)", x
print "(1x,'x =',Z16,' Hex')", x
x=x-1
print "(1x,'x-1=',F20.17)", x
print *
end
샘플 5
C에서 부동 상수는 기본적으로 double입니다. "f"를 사용하여 "89.95f"와 같이 부동 소수자 값을 나타냅니다.
/* Compile options needed: none
*/
#include <stdio.h>
void main()
{
float floatvar;
double doublevar;
/* Print double constant. */
printf("89.95 = %f\n", 89.95); // 89.95 = 89.950000
/* Printf float constant */
printf("89.95 = %f\n", 89.95F); // 89.95 = 89.949997
/*** Use double constant. ***/
floatvar = 89.95;
doublevar = 89.95;
printf("89.95 = %f\n", floatvar); // 89.95 = 89.949997
printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000
/*** Use float constant. ***/
floatvar = 89.95f;
doublevar = 89.95f;
printf("89.95 = %f\n", floatvar); // 89.95 = 89.949997
printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
}
피드백
다음에 대한 사용자 의견 제출 및 보기