주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

원문 : The Perils of Floating Point.



제가 "The Perils of Floating Point" 를 작성할 때, 이게 어느 정도까지 일이 커질지 알지 못했습니다. 그 문서에 수 백개의 링크가 걸렸고, 수 천번 참조되었습니다. 대학이나 프로그래밍 문서, 그리고 출간된 책들이나 많은 블로그들에서 참조되었죠. 얼마나 많은 사람들이 이 도움을 받게 되었는지 알고 싶네요.


The Perils of Floating Point by Bruce M. Bush © 1996 Lahey Computer Systems, Inc.

원문에 대한 표기와 함께 복제가 허용됩니다.


The Perils of Floating Point by Bruce M.Bush


최근 수십년 동안에 있었던 수 많은 훌륭한 엔지니어링과 과학적 모험들은 디지털 컴퓨터의 floating-point( 부동소수점 ) 기능없이는 불가능했을 것입니다. 하지만 여전히 일부 부동소수점 계산의 결과는 좀 이상하고, 심지어는 수학적 경험을 몇 년 동안 해 온 사람들에게조차도 이상합니다. 저는 이러한 이상한 결과들이 발생하는 원인에 대해서 설명하고 적용가능한 제안들을 제공할 계획입니다.


부동소수점 표현과 연산( arithmetic )은 부정확합니다. 하지만 그게 대부분의 프로그래머들에게 특별히 문제가 된다고는 생각하지 않습니다. 많은 입력값들이 내재적으로 부정확한 양( measurement )입니다. 그래서 출력 값에 대한 질문은 에러가 존재하느냐가 아니라 얼마나 많은 에러가 예상되느냐입니다. 그래서 여러분이 부동소수점을 가지고 컴퓨터보다 더 정확한 결과를 암산할 수 있다면, 의혹을 가지기 시작할 것입니다.


저는 몇 가지 이유로 FORTRAN 을 사용해 예제를 프로그래밍 했습니다 :

  1. 포트란에서는 다른 컴퓨터 언어보다 더 많은 부동소수점 계산이 수행됩니다.
  2. Lahey Computer System 이라는 회사에서 일하고 있는데, FORTRAN 언어 시스템을 개발하고 판매하고 있습니다.


이상한 결과들이 나오는 원인의 핵심은 하나에 기반합니다 : 컴퓨터에서 부동소수점이 보통 2 진수라는 것입니다. 반면에 외부적인 표현은 10 진수입니다. 우리는 1/3 이 정확하게 표현되지 않을 것이라 생각하지만, 그것은 직관적입니다. .01 이 되죠. Not so! IEEE 단일정밀도 형식( single-precision format )에서 .01 은 정확히 10737418/1073741824 이거나 대략 0.009999999776482582 입니다. 여러분은 다음과 같은 코드를 확인하기 전까지는 그 차이를 인지하지도 못할 것입니다:




10 진수 부동소수점 구현은 이런 변칙을 포함하지 않습니다. 하지만 10 진수 부동소수점 구현은 거의 보기가 힘듭니다. 왜냐하면 2 진수( binary ) 연산이 디지털 컴퓨터에서는 훨씬 빠르기 때문입니다.


Inexactness


디지털 컴퓨터에서 부동소수점 연산은 내재적으로 부정확합니다. 32 비트 부동소수점 수의 ( 숨겨진 비트를 포함한 ) 24 비트의 가수( mantissa )는 약 7 개의 유효 십진수( significant decimal digit ) 를 표현합니다. 예를 들어 대략 8,388,607 개의 단일정밀도 숫자가 1.0 과 2.0 사이에 존재합니다. 반면에 1024.0 과 2024.0 사이에는 단지 8191 개만이 존재합니다.


모든 컴퓨터에서, 수학적으로 동등한 표현식이 부동소수점 연산을 사용하면 서로 다른 결과를 산출할 수 있습니다. 다음 예제에서, Z 와 Z1 은 보통 서로 다른 값을 가지게 되는데, (1/Y) 혹은 1/7 이 이진 부동소수점에서 정확하게 표현되지 않기 때문입니다.




Insignificant Digits



다음 코드 예제는 유효 숫자로 보일 수 있는 무의미한 숫자들이 나오는 현상을 설명합니다.




단일 정밀도 (REAL) 요소는 대략 최대 7 개의 십진수 정밀도를 표현할 수 있습니다. 그래서 위의 빼기는 (1000.200 - 1000.000) 을 표현합니다. 그러므로 그 결과는 약 3 개의 십진수만을 표현할 수 있습니다. 하지만 프로그램은 "0.200012" 를 출력할 것입니다. 1000.2 는 이진 부동소수점에서 정확하게 표현되지 않고 1000.0 도 그렇기 때문에, 결과 A 는 0.2 보다 약간 큽니다. 하지만 컴퓨터는 ".200" 뒤쪽에 있는 숫자가 무의미하다는 것을 알지 못합니다.


아마도 언젠가는 컴퓨터가 결과에 있는 여러 개의 비트들에 대한 추척을 하겠죠. 하지만 지금은 여전히 그것은 프로그래머의 책임하에 있습니다. 만약 여러분이 데이터형에 의해 표현되는 십진수의 개수에 주의한다면, 유효 숫자의 개수를 근사계산하는 것은 직관적입니다. 하지만 아마도 시간이 걸리는 작업이 되겠죠. 가장 주의해야 할 점에 대해서 이야기해 보도록 하겠습니다:

  1. 뺄셈은 값이 거의 같습니다.
  2. 덧셈은 가수값이 거의 같습니다. 하지만 부호( sign )가 반대인 숫자에서는
  3. 덧셈이나 뺄셈은 매우 다른 가수값을 반환합니다.



Crazy Conversions


정수로의 변환은 부동소수점 수에서의 부정확함을 드러낼 수 있습니다. 다음 예제에서 설명할 것입니다. 21.33 에 가장 가까운 단일정밀도 부동소수점 수는 21.33 보다는 약간 작습니다. 그래서 100. 을 곱하게 되면, 그 결과 Y 는 2133.0 보다 약간 작습니다. 만약 여러분이 Y 를 일반적인 부동소수점 형식으로 출력한다면, 반올림( rounding )이 발생해서 2133.00 으로 출력될 것입니다. 하지만, Y 를 정수에 할당하게 되면, 반올림이 발생하지 않습니다. 그래서 그 숫자는 2132 가 됩니다.



다음 프로그램은 Lahey 의 LF90 으로 컴파일하면 "1.66661000251770" 을 출력합니다.



여러분은 "왜 단일정밀도 수에 무작위인 것으로 보이는 '000251770' 이 붙은 걸까" 라는 의문이 들 것입니다. 글쎄요, 그 숫자는 무작위 숫자가 아닙니다; 컴퓨터의 부동소수점은 이진 표현에서 0 을 사용해 패딩( padding )함으로써 변환을 수행합니다. 그래서 D 는 X 와 정확히 같죠. 하지만 15 개의 십진수로 출력한다면, 부정확함이 드러나는 것입니다. 이것은 비유효( insignificant ) 숫자의 다른 예이기도 합니다. 단일정밀도 숫자를 배정밀도( double-precision ) 숫자로 변환하는 것은 유효 숫자의 개수를 증가시키지 않는다는 것을 기억하십시오.


Too Many Digits


여러분은 이전의 프로그램 예제를 사용해 D 와 X 를 같은 형식으로 출력함으로써 값을 확인해 보려고 할 것입니다:



일부 FORTRAN 구현에서는 두 숫자가 동일한 값을 출력합니다. 여러분은 만족하고 떠나겠죠. 하지만 실제로는 단일 정밀도 수의 낮은자리 숫자( low-order digits )가 출력되지 않는 것을 잘못 판단한 것입니다. Lahey FORTRAN 에서는 그 숫자들이 다음과 같이 출력됩니다.


1.66661000000000       1.66661000251770


이렇게 되는 이유는 매우 단순합니다: Formatted I/O 변환 루틴들은 절대적인 최대 십진수를 알고 있으며, 단일정밀도 요소를 출력할 때 모든 유효숫자는 9 가 됩니다. 나머지 부분들은 현재 "precision-fill" 문자로 채워집니다. 그것의 기본값은 "0" 입니다. Precision-fill 문자는 다른 ASCII 문자로 대체될 수 있는데요, '*'( asterik ) 나 ' '(blank )같은 것들을 예로 들 수 있습니다. Precision-fill 문자를 "*" 로 변경하는 것은 낮은자리 숫자의 비유효성을 강조합니다.


1.66661000******       1.66661000251770


Too Much Precision


IEEE 단일정밀도 형식은 23 비트의 가수, 8 비트의 지수, 1 비트의 부호로 구성되어 있습니다. 펜티엄( Pentium )과 같은 인텔( Intel ) 마이크로 프로세서에서는 내부 부동소수점 레지스터가 64 비트의 가수, 15 비트의 지수, 1 비트의 부호로 구성됩니다. 이는 다른 구현들보다 해상도 손실이 훨씬 더 적은 중간 계산( intermediate calculation )을 가능하게 합니다. 이것의 단점은 중간 값들이 레지스터에 유지되는 방식에 따라서 똑같아 보이는 계산의 결과가 달라질 수 있다는 것입니다.


다음 예제에서, 컴파일러는 A/B 를 계산해서 중간 결과를 단일 정밀도 임시 변수에 저장하고, X/Y 를 계산해서 임시 변수에서 빼고, 결과를 Z 에 저장하는 코드를 생성합니다. Z 는 0 이 아닐 것입니다. 왜냐하면 단일정밀도 임시변수에 저장하면서 해상도가 손실될 것이기 때문입니다. 만약 생성된 코드가 중간 값을 레지스터에 유지한다면, 해상도는 손실되지 않을 것이고, Z 는 0 이 될 것입니다.



다음 예제는 이전 예제의 변형들에 대해서 설명합니다. 컴파일러는 여전히 중간 결과 C 를 레지스터에 유지하는 코드를 생성할 수 있습니다. 이건 Z 값이 0 임을 의미합니다. 만약 A/B 를 C 에 넣음으로써 해상도가 손실된다면, Z 는 0 이 아닐 것입니다.



C 가 레지스터에서 유지되는 것을 막기 위해서 레이블( label ) 100 을 추가했습니다. 그래서 Z 는 아마도 거의 대부분의 컴파일러에서 0 이 아닐 것입니다.



Safe Comparison


다양한 컴퓨터들은 다양한 비트를 사용해 부동소수점 숫자를 저장합니다. 심지어는 같은 IEEE 형식을 사용해 숫자를 저장하고 있더라도, 계산하는 동안에는 다른 일이 벌어질 수 있습니다. 왜냐하면 중간 레지스터의 크기가 다르기 때문입니다. 이식을 증대하고 일정한( consistent ) 결과를 보장하기 위해서, 저는 FORTRAN 에서 실수의 정확한 동일성( equality )을 비교하는 것은 좋지 않다고 생각합니다. 더 좋은 기법은 두 숫자의 차이의 절대값을 비교하는 것인데, 이 때 적절한 엡실론( epsilon )값을 사용해서 거의 같은 관계, 명확하게 더 큰 관계 등을 얻는 것입니다. 예를 들어 :



비교 대상 중 하나를 사용하여 엡실론에 곱셈을하면 그 숫자의 범위가 조정됩니다. 그래서 하나의 엡실론을 여러 가지 비교를 위해 사용할 수 있습니다. 가장 예측 가능한 결과를 얻으려면 엡실론의 절반 정도로 큰 값을 사용하고 다음 예제에서와 같이 비교 대상의 합계를 곱합니다:



부동 소수점 계산이 수학적으로 가능하지 않은 값을 생성할 수 있기 때문에, 큼 이나 작거나 같음 등의 비교도 예기치 않은 결과를 생성 할 수 있습니다. 다음 예제에서 X 는 항상 수학적으로 J 보다 큽니다. 그래서 X/J 는 1.0 보다는 항상 커야 합니다. 하지만 큰 숫자의 J 를 사용했을 때 델타( delta )의 합은 X 로 표현되지 않습니다. 왜냐하면 가수 크기를 넘어서기 때문이죠.



Programming with the Perils


쉬운 답은 존재하지 않습니다. 앞에서 설명했던 방식으로 동작하는 것은 이진 부동소수점의 본성입니다. 컴퓨터 부동 소수점의 힘을 이용하려면 그 한계를 알아야하며 그 한계 내에서 작업해야합니다. 부동 소수점 연산을 프로그래밍 할 때 다음 사항을 염두에 두면 많은 도움이됩니다 :

  1. 단일 정밀도 IEEE 형식에서는 약 7 자릿수, 배 정밀도 IEEE 형식에서는 약 16 자릿수만 표현할 수 있습니다.
  2. 숫자가 외부 십진수에서 내부 이진수로 혹은 그 반대로 바뀔 때마다 정밀도가 손실 될 수 있습니다.
  3. 항상 안전한 비교를 사용하십시오.
  4. 덧셈과 뺄셈은 결과에서 실제 유효 숫자를 빠르게 침식할 수 있다는 것에 주의하십시오. 컴퓨터는 실제로 어떤 비트가 중요한지 알지 못합니다.
  5. 데이터 유형 간의 변환은 까다로울 수 있습니다. 배 정밀도로의 변환이 실제 유효 비트수를 늘리지는 않습니다. 부동소수점 숫자가 더 큰 정수로 출력되는 경우에도 정수로의 변환은 항상 0 으로 잘립니다.
  6. 두 가지 다른 부동소수점 구현에서 동일한 결과를 기대하지 마십시오.


제가 여러분에게 부동소수점 연산의 내부에서 어떤 일들이 발생하고 있는지 좀 더 알려줬기를 바랍니다. 그리고 그 이상한 결과들에 대해서 이해할 수 있게 되었기를 바랍니다.


"위험"중 일부는 피할 수 있지만 대부분은 이해하고 받아들일 필요가 있습니다.


IEEE Standard Floating-Point Formats


IEEE (Institute of Electrical and Electronics Engineers, Inc.)는 부동 소수점 표현 및 계산 결과에 대한 표준을 정의했습니다 (IEEE Std 754-1985). 이 절에서는 부동 소수점 수를 나타내는 IEEE 표준에 대한 개요를 제공합니다. 여기에 포함 된 데이터는이 기사의 나머지 부분에서 일부 내용을 설명하는 데 도움이되지만 기본 개념을 이해하는 데 반드시 필요한 것은 아닙니다.


대부분의 이진 부동 소수점 수는 1.ffffff x 2^n 으로 나타낼 수 있습니다. 여기서 1 은 정수 비트이고, f 는 소수 비트이며 n 은 지수입니다. 정수 비트와 소수 비트의 조합을 가수 ( 또는 significand )라고 합니다. 대부분의 숫자는 정수 비트에 1이 있도록 지수를 조정할 수 있기 때문에( 정규화라고 부르는 프로세스 ), 1을 저장할 필요가 없으므로 효율적으로 추가적인 정밀도를 허용하게 됩니다. 이 비트를 숨겨진 비트라고합니다. 숫자는 부호있는 가수로 표현되므로, 음수는 동일한 크기의 양수와 동일한 가수를 갖지만 부호 비트가 1 입니다. 바이어스( bias )라고 하는 상수가 지수에 더해져 모든 지수는 양수가 됩니다.


지수가 0이고 가수가 0 인 값 0.0 은 음수 기호를 가질 수 있습니다. 음수 0 은 대부분의 프로그램에서 분명하지 않은 미묘한 속성을 가지고 있습니다. 0 이 아닌 가수를 가진진 지수 0 은 "비정규 수( denormal )"입니다. 비정규 수는 크기가 너무 작아 정수 비트 1 로 표현 될 수가 없고 하나의 유효 비트보다 작은 값을 가질 수 있습니다.


지수의 비트값이 모두 1 인( 가장 큰 지수 ) 경우에는 특수한 숫자를 표현합니다. 가수가 0 이면 무한대( 양수 또는 음수 )를 표현합니다. 가수가 0 이 아니면 NAN( not-a-number )을 표현합니다. 잘못된 수치 연산의 결과로 발생하는 NAN 에 대해서는 이 기사에서 더 설명하지 않습니다.


IEEE 표준은 32 비트 및 64 비트 부동 소수점 표현을 정의합니다. 32 비트 ( 단일 정밀도 ) 형식은 상위에서 하위로, 부호 비트, 127 의 바이어스가 있는 8 비트 지수, 23 비트의 가수를 가집니다. 64 비트 ( 배 정밀도 ) 형식은 부호 비트, 1023 의 바이어스가 있는 11 비트 지수, 52 비트의 가수를 가집니다. 숨겨진 비트를 사용하면 정규화된 숫자의 유효 정밀도는 각각 24 비트와 53 비트입니다.


Single-precision format

31, 30-23, 22-0

S, Exponent, Significand


Double-precision format

63, 62-52, 51-0

S, Exponent, Significand


Bibliography


American National Standards Institute (1978), "American National Standard, Programming Language FORTRAN", ANSI X3.9-1978, ISO 1539-1980 (E).


IEEE Computer Society (1985), "IEEE Standard for Binary Floating-Point Arithmetic", IEEE Std 754-1985.

'물리_수학_기하학' 카테고리의 다른 글

중력과 수직항력  (10) 2019.07.07
장력( Tension )  (13) 2019.06.26
Matrix major & multiplication  (2) 2018.08.12
[ 번역 ] Depth Precision Visualized  (11) 2017.06.04
[ 번역 ] Moment of inertia 일부 번역  (0) 2016.11.07
[ 번역 ] 캐릭터 애니메이션 : 스켈레톤과 역운동학  (0) 2016.10.01
모멘트( moment )  (22) 2016.01.06
Curve  (0) 2015.10.04
[ 일부 번역 ] CLIPPING USING HOMOGENEOUS COORDINATES  (0) 2013.04.29
Arc length of curve  (0) 2012.09.21

+ Recent posts