본문 바로가기
CS/컴퓨터구조

[컴퓨터구조] 3 컴퓨터 연산 (3.5 부동 소수점 ~)

by dingwoon 2024. 11. 5.

※"컴퓨터 구조 및 설계 6판 MIPS EDITION" 책을 간단하게 정리한 내용의 글입니다.※

3.5 부동 소수점

프로그래밍 언어는 부호있는 정수와 부호없는 정수뿐만 아니라 소수 부분을 갖는 수도 다룰 수 있어야 한다.

  • 과학적 표기법(scientific notation)
    숫자를 10 미만의 수와 10의 거듭 제곱의 곱 형태로 나타내는 것을 과학적 표기법이라고 한다.
    예를 들어 21,212,421를 2.1212421 * 107로 표기하는 것이다. 또는 0.0123 * 102도 과학적 표기법이다.
  • 정규화된 수(normalized number)
    과학적 표기법에서 맨 앞에 0이 나오지 않는 것을 정규화된 수라고 한다.
    예를 들어서 0.001234을 1.234 * 10-3으로 표기하는 것이다.

정규화된 수를 이진수 형태로 표기하는 것을 이진 소수점(binary point)라고 하고, 이런 수를 지원하는 컴퓨터 연산을 부동 소수점(floating point)라고 한다.
소수점의 위치가 고정되어 있지 않고 떠서 움직인다고 해서 floating point이다. 다음과 같은 형식을 띈다. 정규화된 수이고, 이진수이기 때문에 반드기 1로 시작한다.
1.xxxxxtwo * 2yyyy

  • 부동 소수점 방식의 장점
    부동 소수점을 포함한 자료의 교환을 간단하게 한다.
    숫자의 형태가 정해져 있기 때문에 부동 소수점 간의 산술 알고리즘이 간단해진다.
    불필요하게 선행되는 0이 없기 때문에 한 워드 내에 저장할 수 있는 수의 정밀도를 증가시킨다.

< 부동 소수점 표현 >

부동 소수점은 두 부분으로 나뉜다. 소수 부분(fraction)지수 부분(exponent)이다. 위의 이진 소수점 방식에서 소수점 뒤의 수를 소수 부분이라고 하고, 뒤에 곱해지는 2의 거듭 제곱의 지수를 지수 부분이라고 한다.
수를 32비트로 나타낸다고 했을 때, 이 두 부분을 적절히 나눠야 한다. 소수 부분이 커져서 정밀도가 올라가면 표현 범위가 줄어들 것이고, 지수 부분이 커져서 표현 범위가 늘어나면 정밀도가 낮아질 것이다.


MIPS에서는 위의 그림과 같이 맨 1비트는 부호를 표시한다. 그리고 그 뒤의 8비트는 지수 부분을 표시하고, 또 그 뒤의 23비트는 소수 부분을 표시힌다.부호를 S라 하고, 지수를 E라하고, 소수를 F라할 때, 부동 소수점은 다음과 같다.
(-1)S * F * 2E

< 오버플로(overflow)와 언더플로(underflow) >

숫자가 너무 크거나 너무 작으면 지수부분의 8비트로 표현하지 못하게 된다.
이때 숫자가 너무 커서 지수 필드에 들어갈 수 없는 것을 오버플로라고 하고, 숫자가 너무 작아서 지수 부분의 절댓값이 너무 커져서 지수 필드에 들어갈 수 없는 것을 언더플로라고 한다.

그리고 언더플로와 오버플로의 발생 가능성을 줄이기 위해 지수 부분이 더 큰 자료형이 있다. C언어에서는 이것을 double이라고 하고, double 형식을 갖는 수의 연산을 2배 정밀도(double precision) 부동 소수점 연산이라고 한다. (앞서 설명한 32비트 표현은 단일 정밀도(single precision) 부동 소수점 연산이라고 한다.)


MIPS에서는 위의 그림과 같이 2배 정밀도를 표현한다. 2배 정밀도 연산은 지수 부분이 커져서 오버플로나 언더플로의 가능성을 낮춰주지만, 더 큰 장점은 더 큰 소수 부분을 제공해서 정밀도가 높아진다는 것이다.

< 부동 소수점의 IEEE 754 인코딩 >

앞서 본 단일 정밀도 표현이나 2배 정밀도 표현은 모두 MIPS만의 것이 아니라 IEEE 754 부동 소수점 표준이다.
단일 정밀도와 2배 정밀도 모두 특수한 경우를 제외하고는 소수부분 23비트와 52비트에 소수점 앞의 1을 붙여서 계산한다. 이때 23비트와 52비트를 소수 부분이라고 하고, 소수점 앞의 숨겨진 1을 포함하는 24비트와 53비트를 유효 자리(significand)라고 한다.


IEEE 754 인코딩에서 지수부분이 0일 때와 최댓값일 때는 따로 예약이 되어 있다. 이때 0이나 denormalized number나 무한대, NaN 등을 표현한다.

IEEE 754 인코딩에서 바이어스된 표현법(biased notation)이라는 것이 존재한다.
부동 소수점으로 표현했을 때 크기 비교시에 앞쪽 지수 부분만으로도 크기 비교를 빠르게 할 수 있는데, 이때 지수 부분에 부호가 있게 되면 크기 비교가 복잡한 방식이 되어 버린다. 그래서 크기 비교를 단순하게 하기 위해 지수 부분을 양수로만 표현하고, 바이어스 값을 통해 지수 부분을 조정한다.

단일 정밀도의 경우에는 바이어스값 127을 사용해서 -1을 -1 + 127 = 126ten = 0111 1110two로 표현하고, +1은 1 + 127 = 128ten = 1000 0000two로 표현한다. 2배 정밀도의 경우에 바이어스 값은 1023이다.
이렇게 되면 단일 정밀도에서 가장 작은 양수는 0 00000001 00000000000000000000000two이 된다.
또한 단일 정밀도에서 최대 지수는 254 - 127 = 127이되고, 최소 지수는 1 - 127 = -126이 된다. 여기서 0과 255는 제외된 이유는 위에서 봤듯이 다른 용도로 예약되어 있기 때문이다.

< 부동 소수점 덧셈 >

  1. 지수 부분이 다르면 지수 부분이 작은 것을 지수 부분이 큰것과 동일하게 맞추고, 지수 부분이 작은 수의 소수 부분을 오른쪽으로 자리이동 한다.
  2. 지수 부분이 같았거나 같아졌기 때문에 이제 유효자리끼리 더한다.
  3. 오버플로와 언더플로를 검사하면서 정규화된 과학적 표기법으로 변환한다.
  4. 유효자리를 넘어가는 경우 유효자리에 맞게 반올림 한다.

< 부동 소숫점 곱셈 >

곱셈의 경우 덧셈 처럼 소숫점을 맞출 필요가 없다. 지수를 더하면 곱셈이 된다.

  1. 바이어스 없이 지수를 더한다.
  2. 유효자리끼리 곱한다.
  3. 정규화되어 있는지 확인하기 위해 유효자리끼리 곱한 것을 확인한다. 그리고 오버플로와 언더플로를 검사하기 위해 지수도 확인한다.
    정규화 되어있지 않으면 졍규화한다.
  4. 유효자리에 맞게 반올림 한다.(자리맞춤 한다.)
  5. 피연산자의 부호에 따라 곱셈 결과의 부호를 결정한다.

< 정확한 산술 >

정수는 가장 작은 수와 가장 큰 수 사이의 모든 수를 정확하게 나타낼 수 있다. 하지만, 부동 소숫점 숫자는 실제로 나타낼 수 없는 수의 근사값인 것이 보통이다. 이는 실수는 그 수가 무한대이고, 이는 2배 정밀도 부동 소수점 표현 방법을 사용하더라도 모두 표현할 수 없다. 우리가 할 수 있는 것은 가장 근접한 부동 소수점을 구하는 것이다.

정확한 산술을 위해 IEEE 754는 계산하는 동안 오른편에 항상 2개의 추가 비트를 유지한다. 보호 비트(guard bit)자리맞춤 비트(round bit)이다. 연산 결과가 유효자리보다 클 경우 반올림을 해서 자리맞춤을 하는데, 이때 유효 자리 바로 오른쪽에 위치하는 보호 비트(G)와 그 오른쪽에 위치하는 자리맞춤 비트(R)를 사용하게 된다.

예를 들어 덧셈의 결과가 2.3656ten이고 유효자리가 십진수 세 자리인 경우, 자리맞춤할 두 자리는 5와 6이 된다. 이 두 자리가 0~49일 경우 버리고, 51~99일 경우는 올린다. 50은 타이브레이커(tiebreaker)이므로 50%는 버리고 50%는 올린다. 여기서는 56이므로 올리게 되고, 결과는 2.57ten이 된다.

ulp(units in the last place)라고 하는 척도가 있다. 이는 실제 결과와 부동 소수점의 결과의 차이를 나타낸다. 최하위 유효자리 비트 중 2개가 차이가 날 경우 ulp가 2이다.
오버플로, 언더플로, 또는 유효하지 않은 연산의 예외가 없는 한, IEEE 754는 컴퓨터가 1/2 ulp 이내의 수를 사용함을 보장한다.

IEEE 754에는 네 가지 자리맞춤 모드가 있다. 항상 자리올림, 항상 자리내림, 잘라내기, 그리고 가장 가까운 짝수로의 자리맞춤(round to nearest even)이다.
IEEE 754 표준에서는 계산된 결과값이 두 값의 가운데에 위치할 때, 최하위 비트가 홀수이면 더하기 1을하고 짝수이면 잘라내라고 규정한다. 이 방법은 정가운데일 때 항상 최하위 비트를 0으로 만들어준다. 이 방식이 가장 가까운 짝수로의 자리맞춤이다. 이 방식이 가장 많이 사용되고, Java에서는 이 방식만 지원한다.
가장 가까운 짝수로의 자리맞춤을 위해서는 보호 비트(G)와 자리맞춤 비트(R) 외에 세 번째 비트가 필요하다. 이 비트는 점착 비트(sticky bit)로, 자리맞춤 비트 오른쪽에 0이 아닌 비트들이 하나라도 존재하면 1이 된다. 이를 통해 0.50...00ten과 0.50...01ten을 구별할 수 있다.
점착 비트(sticky bit)는 부동 소수점 연산의 정확성을 보장하는데 필수적이며, 잔여 비트가 반올림에 미치는 영향을 최소화할 수 있다.

3.6 병렬성과 산술 연산: 서브워드 병렬성

그래픽이나 오디오 응용 프로그램에서 데이터의 벡터에 같은 연산을 반복 수행한다. 이것을 빠르게 하기 위해 컴퓨터 설계자들은 덧셈기 내부의 올림수 체인을 분할해서 프로세서가 병렬성을 활용해서 동시에 연산을 할 수 있게 하였다. (128비트 덧셈기에 대해 8비트 피연산자 16개, 16비트 피연산자 8개 32비트 피연산자 4개, 64비트 피연산자 2개를 동시에 연산할 수 있다.)

이렇게 큰 워드 내부에 병렬성이 있다고 할 때 이를 서브워드 병렬성이라고 한다. 데이터 수준 병렬성 또는 SIMD(single instruction multiple data)라고도 한다.

3.7 실례: x86의 SSE와 AVX

2011년 Intel은 고급 벡터 확장(AVX)을 도입했다. AVX는 기존의 128비트 레지스터(XMM)를 256비트(YMM)로 확장해 한 번에 더 많은 데이터를 처리할 수 있도록 했다.
AVX는 데이터 병렬성을 최대한 활용하기 위해 고안되었으며, 특히 부동소수점(FP) 연산의 성능을 크게 향상시다. 예를 들어, AVX를 통해 하나의 명령어로 8개의 단일 정밀도(32비트) 또는 4개의 배정밀도(64비트) 부동소수점 연산을 동시에 수행할 수 있다.

3.8 더 빠르게: 서브워드 병렬성과 행렬 곱셈

x86의 AVX 명령어를 통해 기존 C 프로그램의 속도를 7.5배 향상시켰다.

3.9 오류 및 함정

< 오류: 한 비트 왼쪽 자리이동 명령어가 2를 곱해 준 것과 같은 결과를 보이듯이, 오른쪽 자리이동 명령어는 2로 나누어 준 것과 같은 결과를 나타낸다. >

부호없는 정수이면 맞는 말일 수 있다. 하지만 부호있는 정수에 대해서는 부호 비트가 있기에 틀린 말이다.

< 함정: 부동 소수점 덧셈은 결합 법칙이 성립하지 않는다. >

원래 덧셈은 결합법칙이 성립하지만, 부동 소수점 덧셈에 대해서는 아니다. 부동 소수점 수는 실수의 근사치이고 정밀도에 한계가 있기 때문에 부동 소수점의 범위를 넘어가는 매우 큰 수와 작은 수를 더할 때 결합 법칙이 성립하지 않는다.

< 오류: 정수 데이터형에서 사용되는 병렬 수행 방식은 부동 소수점 데이터형에도 똑같이 적용된다. >

앞서 말했듯이 부동 소수점 덧셈은 결합 법칙이 성립하지 않는다. 그렇기 때문에 병렬 연산 시마다 결과가 달라질 수 있다.