부동 소수점 계산의 정밀도 및 정확도

원본 KB 번호: 125056

요약

부동 소수점 계산의 정밀도, 반올림 및 정확도가 프로그래머에게 놀라운 결과를 생성할 수 있는 많은 상황이 있습니다. 다음 네 가지 일반적인 규칙을 따라야 합니다.

  1. 단일 정밀도와 이중 정밀도를 모두 포함하는 계산에서 결과는 일반적으로 단일 정밀도보다 더 정확하지 않습니다. 배정밀도가 필요한 경우 상수를 포함하여 계산의 모든 용어가 배정밀도로 지정되어야 합니다.

  2. 단순 숫자 값이 컴퓨터에 정확하게 표현된다고 가정하지 마세요. 대부분의 부동 소수점 값은 유한한 이진 값으로 정확하게 나타낼 수 없습니다. 예를 들어 는 .1.0001100110011... 이진(영원히 반복됨)이므로 모든 PC를 포함하는 이진 산술 연산을 사용하여 컴퓨터에서 완전한 정확도로 나타낼 수 없습니다.

  3. 결과가 마지막 10진수 위치까지 정확하다고 가정하지 마세요. "true" 답변과 부동 소수점 처리 장치의 유한한 정밀도로 계산할 수 있는 항목 사이에는 항상 작은 차이가 있습니다.

  4. 두 부동 소수점 값이 같은지 아닌지 비교하지 마세요. 이것은 규칙 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에서 부동 상수는 기본적으로 두 배입니다. "f"를 사용하여 "89.95f"와 같이 float 값을 나타냅니다.

/* 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
   }