Q: 컴퓨터는 어떻게 숫자를 저장할까?

우리고 알고 있는 문자, 숫자들을 컴퓨터 에서 사용하려면, 컴퓨터에게 ‘지정’을 해주어야 합니다. 메모리는 기본적으로 비트를 저장하고, 사용하는데, 비트밖에 모르는 이 메모리에게 우리가 알고 있는 숫자와, 문자를 사용하기 위해서 어떻게 해야할까? 라는 고민 에서 출발합니다.

예를 하나 들어보겠습니다. 용산에 32비트 컴퓨터를 사러갔는데.( 16,64 비트 등…의 컴퓨터들이 있는데, 쉬운 예를 위해서 32비트 컴퓨터로 했습니다.) RAM이 4기가인 컴퓨터는 40만원, RAM이 16기가인 컴퓨터는 50만원 입니다. 이때 용산 아저씨가 딜을 시전 합니다. 16기가 RAM이 장착돤 컴퓨터 45 만원에 판매하겠다고 합니다. 이때 컴퓨터를 사야 할까요?

정답은 컴퓨터를 사지말고 아저씨한테 화를 내고 RAM이 4G 컴퓨터를 구매한다고 해야합니다 입니다. 왜일까요?

메모리는 비트를 저장하고, 사용합니다. RAM이 4G 라는것은, 메모리가 한번에 저장, 사용 할수 있는 총 량이 4기가 라는 의미 입니다. 1비트는 2^1 만큼의 정보를 저장할수 있습니다. 2비트는 2^2(2,8,10,16 진수 설명은 생략 하겠습니다.)

1 bit는 = 2^1 만큼의 정보를 저장할수 있습니다 -> 0, 1

2 bit는 = 2^2 만큼의 정보를 저장할수 있습니다 -> 00, 01, 10, 11

3 bit는 = 2^3 만큼의 정보를 저장할수 있습니다 -> 000, 001, 010, 011, 100, 101, 110, 111,

1byte = 8bit 입니다. = 2^8 = 1byte

1KB = 1024byte = 2^10 byte = 1024 byte

1MB = 1024KB = 2^20 byte = 1,048,576

1GB = 1024MB = 2^30 byte = 1,073,741,824

4G RAM 은 한번에 = 1,073,741,824 * 4 = 4,294,967,296 = 2^32 만큼의 데이터를 옮길수 있습니다.

그래서 엄밀하게 정확한 예시는 아니지만, 위의 이유로 인해서, 16G RAM 을 사용하게되면, 총 한번에 옮길수 있는 양은 정해져있는데, 놀고 있는 RAM이 생기게 됩니다. 위의 예시를 든 이유는, FloatPointNumber 의 정밀도 때문인데, 자세한 설명은 아래서 하겠습니다.

고정소수점과 부동 소수점

비트수 양수의 범위 2의 보수의 범위
8 0~255 -128~127
16 0~65,535 -32,768 ~ 32,767
32 0~ 4,294,967,295 -2,147,483,648 ~ 2,247,483,647

2의 보수 변환 방법은 구글링!

일단 여기까지만 봅시다. 정수를 넘어서면 수학자들이 유리수(rational numbers) 라 부르는 두수의 비율로 나타낼 수 있는 수 있습니다. 0.75, 75/100 등으로 표현이 되고, 42,705.684 라는 숫자를 쪼개에 표현할수 있습니다.

4 * 10000 + 4 * 10000 + 4 * 10^4 +
2 * 1000 + 2 * 1000 + 2 * 10^3 +
7 * 100 + 7 * 100 + 7 * 10^2 +
0 * 10 + 0 * 10 + 0 * 10^1 +
5 * 1 + 5 * 1 + 5 * 10^0 +
6 / 10 + 6 * 0.1 + 6 * 10^-1 +
8 / 100 + 8 * 0.01 + 8 * 10^-2 +
4 / 1000 4 * 0.001 4 * 10^-3

위의 방식으로 변환 할수 있습니다.

  • 고정 소수점

프로그래밍에서 숫자가 중요하는 경우는 금융을 예로 들수 있습니다. 예를들어 1,000,000,000,000만원을 입금했는데, 0이 하나 빠져서 입금된다던지… 하면안되기때문에…

컴퓨터 프로그램에서 조작할 수 있는 돈의 양이 부호와 관계없이 1000만 달러 이상을 초과하는 경우는 없다고 가정 해보겠습니다. 표현 가능한 돈의 범위는 -9,999,999.99 ~ 9,999,999.99 입니다.

4,325,120.25는 다음과 같이 5바이트로 표현 됩니다

2진수 16진수
00010100 00110010 01010001 00100000 00100101 14h 32h 51h 20h 25h

이때, 2진수 맨앞에 있는 첫번째 비트가 1이면 음수, 0이면 양수가 되겠습니다. 지정한 범위를 넘어서는 값을 표현 하려면, 값을 표현하기 위한 5바이트와, 부호를 표현하기 위한 1바이트, 총 6바이트가 필요합니다.(위의 표현식은 음수를 표현했을때 입니다.)

위의 예시처럼 소수점이 고정 되어있는 방식이 fixed-point-format(고정 소수점 형식) 이라고 합니다. 고점 소수점 방식을 사용하는 프로그램은 반드시 소수점의 위치를 알고 있어야합니다. 하지만 장점은, 내가 지정해준 범위 내에서는 오차 없이 연산 할수 있다는 장점이 있습니다.

  • 부동 소수점

490,000,000,000 / 0.00000000026 숫자를 고정 소수점으로 표현하기 위해서는 적어도 12바이트 가 필요합니다. 이러한 숫자를 저장할 때는 과학자들이나 공학자들이 과학적 표기법(scientific notation) 이라는 방식을 사용하는 것이 더 좋을 것 같습니다. 과학적 표기법은 반복하여 사용되는 ‘0을’ 피하기 위해 10의 거듭제곱수를 이용합니다.

490,000,000,000 = 4.9 * 10^11

0.00000000026 = 2.6 * 10^-10

위의 예시에서 4.9와 2.6은 소수부(fraction part), 지수부(characteristic), 또는 가수부(mantissa)라 부릅니다. 하지만 컴퓨터에서 사용할 때는 과학적 표기법에서 해당 부분을 가수(significand)라 부르는 것이 맞을 것 같습니다.

지수부(exponent part)는 10의 거듭제곱수를 나타내는 부분 입니다. 지수부는 가수부에서 소수점이 얼마나 이동할것인지 알려주는 역활을 합니다.

4.9 * 10^11 = 49 * 10^10 = 490 * 10^9 = 0.49* 10^12 = 0.049 * 10^13

-5.812 * 10^7 = -58,125,000 = -5.8125 * 10^-7 = -0.00000058125

이렇게 소수점들이 둥둥 떠다닌다고 해서 float-point-number 라는데… 정확한지는 잘 모르겠습니다. 다른 의견 있으시면 댓글 남겨주시면.. 감사하겠습니다!_!

이런 형식을 과학적 표기법에서 정규 형식(normalized form) 이라 부릅니다. 컴퓨터에서 고정소수점 표기법을 대체하는 것은 바로 부동소수점 표기법 입니다.(좀더 들어가면, 0을 +0 -0 으로도 표기되는 오류가 있습니다.. 컴퓨터 사이언스들은 이런 상황을 매우 싫어한다고합니다.. 자세한건 구글링..)

컴퓨터에서 부동소수점이 사용될때에는 이진수 형태로 사용됩니다. 들어가기전에 소수부가 이진수 처럼 보인다는 점을 이해해야 합니다

0b101.1101

1 * 4 + 1 * 2^2 + 1 * 4 +
0 * 2 + 0 * 2^1 + 0 * 2 +
1 * 1 + 1 * 2^0 + 1 * 1 +
1 / 4 + 1 * 2^-1 + 1 * 0.5 +
0 / 8 + 1 * 2^-2 + 1 * 0.25 +
1 / 16 1 * 2^-3 + 1 * 0.125 +
  1 * 2^-4 1 * 0.0625 +

소수부분을 바꾸는 방법은 2의 역승으로 연산하면됩니다!

컴퓨터에서 왜 이렇게 사용되는지 자세하게 알고 싶으신 분이 계시다면, IEEE(the Institute of Electrical and Electronics Engineers) 에서 1985 년에 제정하고 ANSI(the American National Standards Institute) 에서 승인된 부동 소수점에 대해서 찾아 보시기 바랍니다.

  • IEEE 부동소수점 표준에서는 4바이트를 사용하는 단정도(single precision), 8바이트를 사용하는 배정도(double precision)의 두가지 기본 형태가 정의 되어 있습니다.

1비트 부호(0은 양수, 1은 음수), 8비트 지수부(exponent), 23비트 가수부(significand) 의 세부분으로 이루어지고, 가장 아래 자리가 비트가 오른쪽에 위치 합니다. 32 비트 4바이트 형식으로 이루어져 있습니다.

1-비트 8-비트 23비트 가수부 소수 부분
부호 지수부 significand Fraction

가수부의 경우 정규화된 이진 부동소수점 수 이므로, 소수점 앞에 항상 1의 값을 가지게 되며, 실제로 IEEE 형식에서 부동 소수점 수를 저장 할때는 이 값을 포함하지 않습니다. 23비트 가수부의 경우 소수 부분만 저장되는 것이지요. 따라서 23비트 가수부가 사용되고 있지만, 실제로 24비트의 정밀도를 가지게 됩니다. 24비트 정밀도가 의미하는 바는 잠시후에 이야기 하겠습니다.

8 비트 지수부는 0~255 까지 값을 가질 수 있습니다. 지수부는 편향된 지수(biased exponent) 라 불리는데, 이는 설정된 값에서 편중치(bias)를 빼서, 실제로 적용할 부호화된 지수값을 얻는 방법을 취하고 있기 때문입니다. 단 배정도(single precision)를 가지는 부동 소수점 수에서 편중치는 127이 됩니다(ANSI/IEEE 문서 찾아보시면 확인하실수 있습니다.) s(부호 비트), e(지수부), f(가수부)를 이용하여 다음과 같이 표현할수 있습니다.

(-1)^s * 1.f * 2^e-127

-1의 재곱을 하는게 조금 생소 할수 있지만, 어떤수의 0 승은 1입니다. 실제로 저 식은 ANSI/IEEE 에 자세한 설명이 나와 있습니다.

가장 일반적인 숫자인 ‘0’ 을 표현 해보겠습니다.

  • e가 0이고, f 역시 0 인 경우에는 값을 9 이라 생각합니다. 일반적으로 32비트 수가 모두 0 으로 설정됐다면 이는 일반적인 0을 의미하는 것이지요. 하지만, 부호 비트가 1인 경우에는 음수0 으로 해석하게됩니다. 음수 0은 단정도 형식에서는 표현할 수 없을 정도로 작은 수이며, 0보다 작은 수를 나타낸다(+0, -0 이 값 차이가 있다고 생각하시면 조금 편하실겁니다.)

  • e값은 0이지만 f의 경우 0이 아니라면 이 수는 유효한 수이지만, 정규화 되지 않은 경우입니다. 이 경우에 수는 다음과 같이 해석 됩니다. 소수점 위의 숫자가 1이 아닌, 0이라는 사실에 주목해주세요.

(-1)^s * 0.f * 2^-126

  • 만일 e의 값이 255이며, f가 0 인 경우에는 이는 부호 비트 s에 따라 양수 혹은 음수 무한대를 의미 합니다.

  • 만일 e의 값이 255이며 f의 값이 0이 아닌 경우에는 숫자가 아니라고 보며 이를 짧게 NaN(Not a Number)라 부릅니다. NaN은 연산이 잘못되어서 알 수 없는 숫자가 결과로 나온 예외 경우를 나타 냅니다.

  • 양수와 음수를 따지지 않고 단정도 부동소수점 형식에서 정규화된 이진수 형태로 표현할 수 있는 가장 작은 수는 다음과 같습니다.

0b 1.00000000000000000000000 * 2^-126

소수점 뒤에 0 이 23개 따라오는 형태 입니다. 단정도 부동 소수점 형식에서 정규화된 이진수 형태로 표현할 수 있는 가장 큰 수는

0b 1.11111111111111111111111 * 2^127

십진수로 두 숫자는

1.175494351 * 10^-38 ~ 3.402823466 * 10^38이 되며, 이는 단정도 부동 소수점 표현 형식에서 표현 가능한 범위가 됩니다.

정밀도

2진수로 10진수를 표현할수 있는 범위에 대해서 알아봅시다.

0~10 까지 온전히 표현하기 위해서는 2진수 4자리수가 필요합니다. 이게 무슨 말인가 하면.

1 -> 최대 10진수 까지 1 표현 가능 11 -> 최대 10진수 3 까지 표현 가능 111 -> 최대 10진수 7까지 표현가능 1111 -> 최대 10진수 15 까지 표현 가능

이렇게 2진수 3자리수는 10진수 10 까지 온전하게 표현할수 없습니다. 2진수 2자리수가 되어야 10진수 0~10 까지 온전하게 표현이 가능 하게 됩니다.

위의 생각을 바탕으로 단정도 부동 소수점 형식에서 사용하는 24비트 이진수가 7자리 10진수 수와 대략 비슷할것입니다. 따라서 단정도 부동 소수점 형식은 24비트, 혹은 7자리 10진수 정도의 정밀도를 제공한다고 말할수 있습니다. 그럼 이 말은 무슨 의미일까요?

우리가 알고 있는 수를 컴퓨터가 사용할때는 2진수로 변경하여 사용합니다. 정밀도의 범위는 이때 문제가 생기게 됩니다. 예를들어서 단정도 부동 소수점 수에서는 정밀도 문제로 16,777,216 과, 16,777,217이 동일한 2진수 수로 표현됩니다 이 두수 사이에있는 값도 구분하기가 어려워집니다.

0b 1.00000000000000000000000 * 2^24 == 16,777,216
0b 1.00000000000000000000001 * 2^24 == 16,777,218

16,777,217은??? 16,777,216과 같이 표현되는 문제점이 생깁니다.. 이렇게 ‘정밀도 범위’를 벗어나게되면, 연산시에 문제가 생깁니다. 확인하고 싶으시면

Python 에서는 0.1*0.1 프린트 찍어보시면 우리가 알고 있는 결과랑 다를수 있습니다. 이게 왜 중요 하냐면 예를 들어 우주선을 발사하는 공식에서 작은 오차가 생기게 되면, 목적지에서 점점 멀어지게 되는 계산식을 확인 하실수 있을것 입니다…

  • 배정도(Doubble 정밀도)
1-비트 11-비트 52비트 가수부 소수 부분
부호 지수부 significand Fraction

배정도 부동 소수점 수에서 가장 작은 수는 다음과 같습니다.

1.000….(0이 52개) * 2^-1022

배정도 부동 소수점 수에서 가장 큰 수는 다음과 같습니다

1.111….(1이 52개) * 2^-1022

10진수로 나타내면

2.2250738585072014 * 10^-308 ~ 1.7976931348623158 * 10^308 까지 범위입니다.. 정말 큰수입니다..(배정도는 단정도에서 확장 하시면 됩니다!)

여담

어셈블리어를 사용하는 프로그래머들은 부동소수점 연산 방식을 직접 만든다고 하는데, 대단 하신거같습니다.. 혹시 여기까지 읽었는데 부동 소수점에 대해서 알쏭 달쏭 하시면, 하나만 기억 하시면 됩니다. 부동 소수점은 크고 작은 수 연산에 도움이 되지만 정밀도가 떨어진다!

Reference

CODE: 하드웨어와 소프트웨어 숨어있는 언어 - 찰스 펫졸드
floating point number