Fixed point vs Floating point

 

DSP(Digital Signal Processing) 엔지니어로 일을 하게되면 “Fixed point”라는 개념을 많이 접하게 됩니다. 때문에 기존에 쓰던 덧셈, 뺄셈, 곱셈, 나눗셈, 제곱근과 같은 연산에 exponential이나 log는 연산량을 고려해 알고리즘을 짤 때, 골치아픈 경우가 많았습니다. 그런데 그냥 편하게 Floating point를 사용하면 되지, 왜 구지 Fixed point을 사용할까? Floating point로 프로그래밍에서 계산을 할 때 아래와 같은 결과는 어떻게 나올 수가 있는 걸까요? 이 질문에 답은 Floating point가 고안된 과정을 쭉 훑어보면 나올 수 있습니다.

\[0.1 + 0.1. = 0.20000000298023224 ?\]

영상만드시는 분께 허락을 받아 youtube.com/watch?v=dQhj5RGtag0 영상에서 나오는 내용을 제 나름대로 정리한 것임을 미리 언급드립니다. 영어가 익숙하시다면 꼭 영상을 보시는 것을 추천드립니다.

1. Integer representation

컴퓨터에서 수를 표현하기 위해서 어떤 방법이 있을까요? 가장 쉽게 생각 할 수 있는 방법은 “이진수” 입니다.

Binary bit 1 0 0 0 1 0 0 1
  2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
Decimal 128 64 32 16 8 4 2 1

컴퓨터내에 있는 모든 숫자는 0과 1로 표현될 수 있는 것을 이용해 이진 수로 0과 양의 정수를 표현할 수 있죠. 위의 예시는 $100001001_{2} = 137$ 을 보여주고 있습니다. 그렇다면 32bit, 즉 int 혹은 int32로 선언한 변수의 메모리로 우리는 $2^{32}$의 수 까지 표현할 수 있는 것이죠.

2. Decimal representation

여기까지는 이해하는 데 그렇게 어렵지 않죠. 그럼 당연히 정수만 우리는 표현하진 않죠, 소수와 음수는 어떻게 표현할 수 있을까요? 먼저 소수를 살펴봅시다.


0000000000000001.1000000000000000
= 2^0+2^{-1} = 1.5

1111000010011111.1001001010101001
= DEC 61599.5727912354

생각 할 수 있는 간단한 소수를 표현하는 방법은 정수를 표현하기 위한 영역과 소수를 표현하기 위한 영역으로 나누는 겁니다. 위의 첫번째 예시처럼 $2^0+2^{-1}$로 수를 표현하면 우리는 $1.5$를 표현할 수 있죠. 이 방법을 우리는 Fixed point라고 부릅니다. 고정된 점을 이용한다… 이런 의미인 것 같죠?

3. Point index and Digits

그럼 fixed point 표기로 우리는 31개의 소수점자리를 표현할 수 있습니다. 그러면 그 소수점자리를 가리키는 “Point index” 와 실제 숫자인 “Mantissa”로 32bit을 채워 숫자를 표기할 수 있을 겁니다.

Point index(5bit) | Digits(Mantissa)(27bit)
            11001 | 11.1100001001111110010010101
               25 |                  3.759739548

4. How to represent negative number?

그러면 음수는 어떻게 표현할 수 있을까요? 가장 간단한 방법은 Sign bit라고 맨 앞에 1비트가 0이면 양의 수, 1이면 음의 수로 표현하는 겁니다. 이외에도 2’s complement 방식도 있습니다.

5. Redundancy Problem

하지만 위와 같이 소수를 점으로 표현하게 되면 한 가지 문제가 생길 수 밖에 없습니다. 아래와 같이 그건 바로 수를 표현하는 데 공간을 낭비하고, 표현할 수 있는 수도 제한적이며, 이는 같은 메모리 공간에 같은 수를 표현하는 데 여러 표기가 있다는 것이 핵심 문제라는 것이죠.

Redundancy

001.10000000000000000000000000000
0000000000000000000001.10000000000
= DEC 1.5

그래서 이를 해결하는 방법이 바로 과학시간에 많이 배우는 Scientific Notation $4.937 \times 10^9$ 입니다.

6. Scientific Notation

\[4.937 \times 10^9\]

왜 이 표기를 쓰기 시작한 걸까요? 영상에서는 한 숫자를 표현할 수 있는 “유일한 하나의 표현 방법”이 그 이유라고 설명합니다. 여기서 부터 아래의 예시처럼 mantissa는 항상 소수자리를 표현하게 됩니다.

sign | exponent | mantissa
   1   01100010   1.0011100101100110100011

이렇게 된 숫자표기로 표현할 수 있는 범위는 영상 7:50에서 확인해주세요!

그리고 Scientific Notation에서 표기하는 방법을 보면 Mantissa의 첫 번째 bit는 항상 1인 것도 볼 수 있으실 겁니다(10진수는 1-9까지 있지만 2진수에서는 딱 1, 즉 한 bit만 사용하는 것인 거죠!). 여기서 이 항상 1인 digit을 leading bit이라고 부를 겁니다. 이 표현 방법이 바로 “Floating point” 입니다.

sign | exponent |            mantissa
   1   01100010 |1(always)|.|0011100101100110100011

Reference. jan Misali's how floating point works

7. How to represent zero?

근데, 위와 표기하게 되면 문제가 한 가지 있습니다. 영상에서 숫자 범위를 보시면 알겠지만, 0을 표현할 수가 없게 됩니다. 그리고 다른 한 가지 문제는 exponent로 표현을 할 때 숫자 범위에 불균형을 보실 수 있습니다.

그렇게 0을 표기하기 가장 쉬운 방법은? 아래와 같이 모든 exponent의 bit가 0일 때 0이라고 할 수도 있겠죠.

Reference. jan Misali's how floating point works

하지만 그러면 0이라고 표현할 수 있는 숫자가 너무 많아 질 겁니다. mantissa가 모두 0일 때 0이라고 정의해도 되겠지만, 영상에서는 “but it would be nice to put those bits to use so we’re not wasting all these potential number.”라고 이야기 합니다. 아마 비트를 모두 다 활용하는 방법을 사람들이 생각했던 것이겠죠?

이 문제를 해결하는 방법은 전혀 다른 문제를 해결하는 데서 발견하게 됩니다. 그 다른 문제는 “만약 이 시스템에서 가장 작은 두 수를 뺀다면 어떻게 표현해야할까?” 에서 나옵니다. 예를 들어 아래와 같은 상황이 있다고 생각해봅시다.

  1.00011 x 2^{-127}
- 1.00011 x 2^{-127}

----------------------
= 0.00010 x 2^{-127} = 1.0 x 2^{-131}

이 수를 지금 표기방법으로는 0으로 할 수 밖에 없었습니다. 다른 방법은 없을까요? 여기서 중요한 한 가지 포인트가 잡습니다.

zero means that “zero” can actually have all sorts of values.

바로 “subnormal numbers” 입니다. 이 숫자는 “numbers that are too small to be represented normally” 로 만약에 exponent가 가장 작은 값이 되면 leading bit를 0으로 바꿔 버리는 거죠. 아래와 같이요.

sign | exponent | mantissa
   1   00000000   0.0011100101100110100011

그러면 우리는 엄청 작은 수를 표현할 수 있는 숫자 표기법을 가질 수 있게 된 겁니다!

8. Philosophy of floating point, Estimation

“Floating point arithmetic is all about this compromise between precision and being able to use a wide range of numbers.”

이제 우리는 Floating point를 이용하면 $10^{15}$와 같이 어마어마하게 큰 수도 표현할 수 있죠. 하지만, 이 floating point number는 정확하게 그 숫자를 표기하지는 않습니다. “숫자의 범위” 를 표현하죠. 예를 들어서, 3의 경우에도 3이 아닌 3에 가장 가까운 floating point number를 표기합니다. 그럼 표현할 수 있는 범위가 어마어마하게 많겠죠? 그럼에도 mantissa의 bit수를 고려하면 그 범위는 매우 좁을 수 밖에 없을 겁니다. 실제로 3을 표현하는 floating point number다음의 숫자는 $1.10000000000000000000001_2 \times 2^1= 3.0000002384185791015625$ 입니다. 이정도 정밀도면 대부분 계산하는데는 크게 지장은 없을 겁니다. 만약 더 작은 정밀도를 원한다면, 64bit로 늘리면 되겠죠.

아직 숫자 표기에 $-0$이 있습니다. 사실 크게 문제가 될 것 같지 않지만, 위의 수를 표현하고자 하는 철학(?)으로 이 문제 또한 해결하고자 한 표기법이 바로 “IEEE single-precision floating point standard”입니다.

9. How to represent Big number?

Reference. jan Misali's how floating point works

매우 큰 수는 어떨까요? 작은 수와 마찬가지로 exponent가 모두 1인 경우를 가장 큰 수 Infinity라고 두게 된다면, 작은 수떄와 동일하게 mantissa의 숫자가 바뀌어도 똑같은 Infinity를 가리키게 됩니다.

sign | exponent | mantissa
   1   11111111   1.0000000000000000000000
              -  Infinity

sign | exponent | mantissa
   1   11111111   1.0000000000000010000000
                  Not a Number(NaN)

우리는 exponent가 모두 1이며 동시에 mantissa가 모두 0인 경우에 Infinitiy라고 부를 겁니다. 그리고 그 상태에서 mantissa가 모두 0이 아닌 경우를 Not a Number(NaN)이라고 부를 거죠.

10. Calculation between zero and infinity

이렇듯, floating number와 실제 숫자에서 가장 큰 차이는 0이 표현하는 바입니다.

그럼 0과 $\infty$ 에서 몇 가지 조합이 생길 수 있겠네요.

\[\begin{aligned} 1/0 &= \infty\\ -1/0 &= \infty \\ 0/0 &= \text{NaN}\\ 0 \times \infty &= \text{NaN} \end{aligned}\]

네, 바로 0/0 을 정의하기 위해서 Not a Number(NaN)를 정의합니다. $0 \times \infty$의 경우도 [a number that’s too small to store] x [a number that’s too large to store] 인데, 수의 범위를 정할 수 없으니 이 경우도 Not a Number(NaN)가 되는 거죠. NaN은 즉, “Invalid operation”을 위해서 태어난 친구인 것이죠.

11. Epilogue

참고로 Wiki에서 볼 수 있는 IEEE 754 정의하고 있는 32bit Floating point number의 예시는 다음과 같습니다.

Reference. wikipedia-Floating-point-arithemetic

위에서 주의할 점은 leading bit는 이미 fraction에서 빠져 있다는 점이다. 따라서 위를 식으로 쓰자면,

\[\text{exp} = 0*2^{7} + 1*2^{6} + 1*2^{5} + 1*2^{4} + 1*2^{3} + 1*2^{2} + 0*2^{1} + 0*2^{0} - 127\\ frac = 1 + 0*2^{-1} + 1*2^{-1} + 0*2^{-1}... + 0*2^{-23} \\ \text{decimal number} = \text{exp} * \text{frac} = 0.15625\]

여기까지 이해하고 난 다음 든 질문은 “그러면 왜 DSP에서는 Fixed point를 사용하고 있는 거지? 였습니다. Floating point number를 보게되면 부가적인 숫자기호들을 볼 수 있을 겁니다. 예를 들어 NaN, $\infty$ 와 같은 기호들이 있죠. 그 기호들은 어떻게 연산처리해야 할까요? 그 기호들을 추가로 넣는다면 연산량에 차이가 없을까요? 이 Floating point number를 계산하기 위한 하드웨어를 설명하는 영상을 보시면, Fixed point가 특정 분야에서는 쓰일 수 밖에 없다는 것이 어느정도 납득이 갑니다.

연산량이 정말 다를까에 대해서 조금 더 찾아보니, 이 글 에서는 아래와 같이 이야기 합니다.

The paper mentioned by njuffa, Cristina Iordache and Ping Tak Peter Tang, An Overview of Floating-Point Support and Math Library on the Intel XScale Architecture supports this. For the Intel XScale processor the list as latencies (excerpt):

integer addition or subtraction:  1 cycle
integer multiplication:           2-6 cycles
fp addition (emulated):           34 cycles
fp multiplication (emulated):     35 cycles

이와 관련해서는 추후에 테스트를 해보고 추가적으로 덧붙일 예정입니다.

여기까지 정수, 소수, 음수, 더 넓은 숫자를 위한 표기법, 가장 작은 수들, 가장 큰 수들, IEEE가 생각하는 숫자의 철학까지 현재 정의하고 있는 Floating number에 대한 IEEE 흐름을 살펴봤습니다. 이제 숫자를 배워봤으니, 다음 차례로 차근차근 신호처리를 들어가 보겠습니다.

Reference