본문 바로가기
컴퓨터 구조와 운영체제

숫자는 컴퓨터에 어떤 bit형태로 저장될까? 왜 이렇게 쓸까?

by cuziam 2023. 3. 29.

이 글은 정수(Integer)와 부동소수점(Float) 수가 어떤 비트로 표현되는지, 그리고 왜 그렇게 표현되는지에 대해 다룹니다.

* 조너선 스타인하트의 '한 권으로 읽는 컴퓨터 구조와 프로그래밍'을 공부한 내용에 제가 추가적으로 알아본 내용을 정리하였습니다.
* 양의 이진수를 덧뺄셈하는 . 간단한 논리연산은 안다고 가정하고 글을 진행하겠습니다.

아마 프로그래밍을 조금이라도 접해보았다면 프로그래밍 언어에는 다양한 데이터 타입들이 존재한다는 것을 알고 있을 것이다. 그리고 각각의 데이터의 크기가 몇 바이트인지에 대해서도 들어봤을 것이다. 혹시 잘 기억나 지 않는 사람들을 위해 아래의 표를 보고 떠올려보길 바란다. 아래의 표는 C언어를 기준으로 작성한 자료형 별데이터의 범위와 크기이다.

C언어의 숫자 데이터 타입 종류

 

다른 언어로 프로그래밍을 접한 사람에게 C언어는 데이터 타입이 다소 복잡하게 느껴질 것이다. 하지만 지금 은 이걸 외우자는 게 아니다. 다른 프로그래밍 언어들도 대부분 기본자료형이 주어져 있다. 특히 숫자와 문자 ()은 거의 모든 프로그래밍 언어가 사용하고 당연히 이걸 읽고 있는 여러분도 이것을 사용한 적이 있을 것이.

 

이 글에서 살펴보고자 하는 것은 컴퓨터에서 사용되는 이러한 숫자와 문자들이 어떤 비트들로 구성되어 있는지 있는지, 그리고 이런 비트로 구성되어 있는지 알아보고자 하는 것이다. 예를 들어 정수는 32bits(4bytes)되어있는데, 각각의 비트들이 무엇을 표현하는지 그렇게 써야했는지를 알아보고자 하는 것이다.


 Q: 일단 질문 하나. 하필 컴퓨터는 비트라는 언어를 사용해야 했을까?

기계어는 비트들로 되어있다. 근데 하필 비트여야 했을까? 예를 들어 십진수 23 이진법으로 계산하면 10111이다. 근데 23을 표현하는 방법은 사실 다양하다. 뭐 그냥 2323이라고 그대로 두고 써도 됐을 거고 4진법이나 5진법 같은 걸 써도 훨씬 적은 자리 수로 큰 수들을 표현할 수 있었을 것이다. 그런데 왜 하필 이진법으로 사용했는가 말이다.

이는 사실 구체적으로 들어가면 전자회로나 디지털 신호 및 시스템에 관련된 지식이 필요하다. 구체적으로 설 명하는 것은 무리가 있으므로 간단하게 소개하자면 이렇다. 일단 bit는 두 가지 상태를 표현하는 방법임을 기억하자!

 

1. 회로의 신호에서 오류 검출 정정에 있어서 이진법이 유리함.

컴퓨터는 일종의 회로이고, 전기신호(거의 전압값)를 이용하여 정보를 교류한다. 근데 이 전기신호라는 것 은 보낸 그대로 전달되지 않는다. 어떤 회로 소자(장치)가 다른 소자에 0V,5V,0V,5V를 순서대로 전달했을 때, 신호를 수신한 소자는 여러 현실적인 이유로 인해서 다른 전압값을 전달받는다. 예를 들면 0.1V, 4.8V, 0.2V, 5V 이런 식으로 수신될 있다.

 

만약에 0V5V만을 이용하여 통신한다는 것을 송신자와 수신자 둘 다 알고 있다면 오류를 어떻게 발견 및 정정할 수 있을까? 단순하게 생각해 보면 오차가 2.5V이내에서 발생한다고 가정하고 2.5V미만은 0V로,2.5V 이상은 5V 정정하는 방법이 있을 것이다. 그럼 0V 5V 0V 5V 쉽게 정정할 있을 것이다.

 

근데 예를 들어 0, 0.1, 0.2, 0.3, … 5V 통신에 활용한다고 가정해보자. 보낼 있는 전압값의 경우의 수가 많아진 만큼 수신자의 입장에선 오류를 검출 및 정정하기가 어려워진다. 따라서 단순하게 두 가지 전압값을 활용한 체계가 가장 정확하게 정보를 받기가 쉽다. 따라서 비트 수 체계, 01을 사용하는 것이 이런 점에서 유리하다.

 

*몰론 오류를 검출 및 정정하는 방법은 좀 더 다양할 뿐더러, 수식등으로 엄밀하게 표현되지만, 전반적인 컨셉은 이 정도로 직관적으로 알아두는 것이 좋을 같다.

 

2. 컴퓨터에 있는 논리게이트가 가지 전압값만을 input으로 받으므로.

컴퓨터는 논리게이트(논리연산을 하기 위한 전기회로)를 이용하여 연산을 수행하는데, 이 논리게이트들은 내부적으로 트랜지스터라는 일종의 전자 스위치 기능을 수행하는 부품을 연결해서 만든다. 그리고 그 트랜지스터들은 0V(off,low)와 0 아닌 다른 전압값 하나(on,high)를 input으로 사용한다. (+ 가지 압값을 사용하는 이유는 1번에서 설명한 것과 연관이 깊다.) 즉 논리게이트에선 두 개의 전압값을 input으로 사용하므로 비트로 표현하는 것이 자연스럽다.

 

자 그럼 컴퓨터가 왜 비트를 사용하는지는 엄밀하진 않지만 대강의 컨셉은 알았다. 그럼 이제 정말 본론으로 넘어가자.


 Q: 정수는 어떤 비트들로 표현해야할까?

(이진수 덧셈과정, ’부호 크기 표기법, ‘1 보수 표기법, ‘2 보수 표기법에 대해서)

결론부터 말하자면 대부분의 정수형 자료형은 내부적으로 ‘2의 보수표기법을 사용한다. 2의 보수를 사용하는 이유는, 어쩌면 당연한 말이지만 연산 효율이 좋기때문이다. 그리고 2의 보수가 무엇인지, 그리고 왜 연산 효율이 좋은지에 대해서 알아보자. 먼저 일반적으로 이진수를 덧셈하는 과정이 어떤지 살펴보자.

 

*이진수가 덧셈을 수행하는 과정: AND XOR 게이트를 통해서 수행함.

앞서 말했듯이 일반적인 양의 이진수를 표현하고 계산하는 법은 안다고 가정한다. 컴퓨터는 어떻게 이진수를 덧셈할까? 대개 ANDXOR 논리게이트를 활용한다.

십진수 덧셈과 거의 유사하다. 십진수 11 9 더한다고 하자. 일단 크기가 제일 작은 자리 수부터 더한다. 1 과 9를 더하면 10이 된다. 그럼 더한 결과가 10 이상이므로 받아올림(carry)이 발생하고 그 앞자리에 1을 더한. 결과적으로 20 된다.

이진수도 동일하다. 1011과 1001를 더한다고 하면, LSB끼리 먼저 더하고 그 결과가 2이상이면 받아올림이 발생하고 앞자리에 1을 더한다. 결과적으로 10100이 된다. 이런 과정을 컴퓨터 내부에선 ANDXOR 논리게이트를 활용한다.

AND 게이트는 입력이 모두 1일 때만 1을 출력하고, 그 외에는 0을 출력한다. XOR 게이트는 두 입력이 다를 때만 1 출력하고, 같을 때는 0 출력한다. 대략적인 컨셉은 덧셈의 결과를 XOR 연산으로 얻되, CarryAND 연산으로 얻는 것이다.

 

 

오른쪽 자리에서
carry bit
 
1
 
0
 
1
 
1
 
없음
input 1(1011) 없음(0) 1 0 1 1
input 2(1001) 없음(0) 1 0 0 1
XOR 결과 1 0 1 0 0
AND 결과(Carry 판별)  
0
 
1
 
0
 
1
 
1

따라서 덧셈 과정을 정리하면 이렇다.

1. LSB부터 각 자리의 비트를 XOR 게이트로 더한다. 이는 각 자리의 합을 구하는 것과 같다. 예를 들어, 11 더할 때는 XOR 게이트의 입력으 1 1 넣고, 출력으로 0 얻는다.

 

2. 각 자리의 carryAND 게이트로 계산한다. carry는 이전 자리에서의 합산 결과에 대한 자리 올림을 나타 낸다. 예를 들어, 11을 더하는 경우 이전 자리에서의 carry가 존재한다. 이 경우, AND 게이트의 입력으 1 1 넣고, 출력으로 1 한다.

 

3. 이전 자리에서의 carry와 각 자리의 합을 XOR 게이트로 더한다. 이를 통해 다음 자리의 합과 carry를 계산 한다. 예를 들어, 이전 자리에서의 carry1이고, 현재 자리의 합이 1일 때는 XOR 게이트의 입력으로 11 넣고, 출력으로 0 얻는다. 값은 다음 자리에서의 합이 된다.

 

4. 이전 자리에서의 carry 자리의 합을 AND 게이트로 더한다.. 이를 통해 다음 자리의 carry 계산한 . 예를 들어, 이전 자리에서의 carry 1이고, 현재 자리의 합이 1 때는 AND 게이트의 입력으로 1 1 을 넣고, 출력으로 1 얻는다. 값은 다음 자리에서의 carry 된다.

 

 

이제 이진수를 덧셈하는 과정은 알았다. 근데 문제는 이 방법을 지금까진 양의 이진수에만 적용해봤다는 점이.


 *부호 크기 표기법

정수는 양의 정수, 음의 정수, 0으로 구성되어 있다. 만약에 이걸 읽고 있는 당신이라면 어떤 식으로 정수를 표현할 것인가? 가장 직관적인 방법은 비트는 부호를 표현하고 다른 비트들은 크기를 표현하는 방식이 이다. 이러한 방법을 부호 크기 표기법이라고 한다.

구체적으로 말하면 MSB(Most significant bit) 정수가 음수일 1, 양수일 0으로 표기하고 나머지 비트 들은 크기를 표현하는 것이다. 예를 들어 10111이라면 십진수로 -7을 의미하고 01010은 십진수로 10을 의미 한다.

방법은 사람이 읽고 이해하기 간단하기는 한데 가지 문제 발생한다.

 

1.     0을 표현하는 방식이 두 개가 되어서 비트가 낭비된다. ex) 1000이나 0000이나 0 표현한다.

 

2.     덧셈을 AND XOR 이용하여 연산하기가 곤란하다.

ex) 앞서 배운 덧셈 방법을 이용하여 0011(3) 1001(-1) 더해보면 10100(-4)가 된다. AND XOR

연산하면 잘못된 결과가 발생한다.

 

 

*1 보수(1’s complement)

정수를 표현하는 또 다른 방법은 1의 보수라는 표기법이다. 이 역시 컨셉은 간단하다. 부호와 크기 표기법을 약 응용한 방법이다. 양수는 그대로 부호와 크기법으로 사용하되, 음수는 양수를 NOT연산 시킨 방식을 쓰는 것이다. 예를 들어 0110 6이고, 이를 NOT 연산으로 반전시킨 1001 -6 사용하는 것이다.

이러한 방식은 NOT 관계에 놓인 두 수를 서로 더하면 0이 되므로 앞서 배운 방법으로 덧셈을 수행할 수 있을 것으로 보인다. 근데 여기서도 문제가 발생한다.

 

1. 0을 표현하는 방식이 두 개가 되어서 비트가 낭비된다. ex) 0000이나 1111이나 0 된다.

 

2. MSB에서 carry 발생하면 LSB 이를 다시 옮겨서 더해야 한다. 이를 순환올림(end-around carry)이라고 한다. 방식을 사용하면 연산의 오류는 없지만, 순환올림을 처리하기 위한 하드웨어를 추가해야한다.

 

ex)0010(+2)과 1110(-1) 더하면 앞선 방법에선 10000(-15)가 된다. 1 보수 표현에선, 제대로된 결과 를 얻기 위해 carry되었던 맨 앞의 1 LSB 더해주는 과정이 추가된다. 그럼 0001(+1) 된다.

 


* 2 보수(2’s complement)

결과적으로 우리는 하드웨어를 추가할 필요도 없으면서 XOR AND연산만 사용하는 방식이 필요하다. 그래서 최종적으로 나타난 것이 2의 보수 방식이다. 양수는 동일하게 부호와 크기법을 사용하고, 음수는 양수를 반전한 수의 1 더하는 것이다. 예를 들어 0110(6) 반전시키면 1001이고 여기서 1 더한 1010(-6) 음수표현이 된다.

방식을 사용하면 크기와 부호법과, 1 보수법을 사용했을 발생한 문제점을 해결할 있다.

1.     0 표현하는 방식이 1개가 된다.

ex) 십진수 0 이진수 0000으로만 표현된다.

 

2.     덧셈을 할 때 순환올림을 할 필요없이 일반적인 이진수 덧셈과 동일하게, XORAND 연산만으로 결과를 구할 수 있다.

ex) 0010(2) 1110(-2)를 더하면 00000(0) 된다. 0101(5) 1101(-6) 더하면 1111(-1) 된다.

이러한 이유로 인해 컴퓨터 내부에서 정수 자료형들은 대개 2 보수의 형태로 저장된다.


 Q: 실수는 비트로 어떻게 표기해야 할까? (고정소수점 방식과 부동소수점 방식)

정수는 앞서 설명했지만 또 다른 난관이 있다. 실수는 유리수와 무리수를 포함한다. 문제는 소수점이 있어야만 표현되는 숫자들이다. 어떻게 표기해야 할까. 이는 크게 두 가지 방식이 존재한다. 소수점을 특정 자리 수에 고정시켜 수를 표현하는 방식과 소수점을 이동시켜가며 수를 표현하는 방식이 존재한다.

 *고정소수점(fixed-point) 방식

가장 단순한 방법은 소수점의 위치를 고정하고 소수점을 기준으로 비트들을 두 부분으로 나눠서 한 쪽은 정수부분을 한 쪽은 소수 부분를 표현하도록 하는 것이다. 이러한 방식을 고정소수점 방식이라고 한다.

ex1) 4비트 고정소수점 방식을 사용하고 가정. MSB쪽 2비트는 정수부, 다른 2bit 소수부로 사용한다고 하면

1/4, 1/2, 3/4 각각 00 01, 00 10, 00 11 된다.

 

소수 부분은 고정된 정밀도를 가진 값이 된다. 소수부가 2bit 주어진다면 1/4단위로 표현할 있고 3bit

있다면 1/8단위로, 4bit 있다면 1/16 단위로 표현가능하다.


*부동소수점(float-point) 방식

과학적 표기법

하지만 실제로 사용하는 방식은 부동소수점 방식이다. 부동소수점 방식은 고정 소수점 방식에 비해서 크기가 더 큰 수와, 정밀도를 가질 수 있다. 부동소수점이 동작하는 방식은 이렇다. 과학적 표기법을 이용하는 것이다. 십진법에서 과학적 표기법은 다음과 같다..

 


과학적 표기법

이 때 m은 1이상 10미만의 실수이며, n은 정수이다. m은 가수 혹은 유효숫자라고 부르며, n은 지수라고 부른.

ex) 십진수에서 1.4(가수) * 10^-3(지수) 같은 것이 과학적 표기법이다.

 

-이진수로 표현된 과학적 표기법

그럼 위와 같은 과학적 표기법을 이진법 체계로 표현하면 어떨까? 이진수 체계에선 가수와 지수를 전부 이진수로 표현한다.

이 때 m은 이진수로 표현된 위와 동일한 범위의 실수이며, n은 이진수로 표현된 정수이다. 지수의 밑이 2인 것 빼면 사실상 동일하다.

 ex1) 과학적 표기법으로 작성된 이진수 0110가 있다고 하자. 그리고 MSB 부호, 오른쪽 bit 가수, 나머지 비트가 수라고 하자. 그럼 이것은 십진수로 1 2^1 나타낸 것이다. (이것을 십진수로 풀면 , 2.0 동일하다.)

 

ex2) 1010 에서 MSB 부호, 오른쪽 2bit 크기, 다른 하나가 지수라고 하면 부호가 음수(1), 크기가 01, 지수가 1임 즉 십진수로 1 2^1을 의미한다.

 

이런 식으로 부호, 가수, 지수로 표현된 부동소수점 표현 방식은 국제표준(IEEE 754)으로서 받아들여지고 있 다. 좀 본격적으로 살펴보면 이런 식으로 표현된다.

 

심화!) 실수 42.75를 부동소수점으로 표현해보자. 이것은 32비트 단정도 부동소수점 표현을 사용한 예시이다. 이 과정을 구체적으로 외울 필요는 없지만 필요한 사람은 읽어보기 바란다.

1. 소수를 이진수로 변환 42.75 = 101010.11 (2진수)

2. 소수점 왼쪽의 가장 왼쪽에 있는 1 바로 오른쪽으로 소수점을 옮긴다(정규화) 이 과정을 수행하면 정밀도를 높일 수 있다.

1.0101011 × 2^5

 

3. 지수에 바이어스를 더한다(32비트 부동소수점의 경우 바이어스는 127입니다):

지수: 5 + 127 = 132

 

4. 부분을 IEEE 754 형식으로 변환한다.

부호(sign): 양수이므로 0

지수(exponent): 132 이진수로 변환하면 10000100

가수(mantissa): 정규화된 수에서 소수점 뒤의 부분만 취한다. 따라서 0101011 (23비트, 부족한 비트 0으로 채워버린다.)

 

5. 부분을 모두 연결한다

0 (부호) | 10000100 (지수) | 01010110000000000000000 (가수)

결과적으로 위의 수가 42.75 32비트 부동소수점 표현이다.

 

숫자는 이 정도로 이 정도로 마치려고 한다. 다음에는 문자를 다뤄보려고 한다. 문자들은 아스키 코드, 유니코드, base64등과 같은 비트 형식으로 문자를 표현한다. 이러한 방식들이 왜 나왔고, 어떤 식으로 사용되는 지에 대해서 알아본다.