[CodeEater와 제로부터 시작하는 C언어] 4.1장 - 변수는 어떻게 표현되는가

아래의 내용은 조금 어렵기 때문에 지금 이해하기 어렵다면 건너 띄어도 좋다.
그리고 이 내용을 증명하기 위해서는 아직 모르는 개념을 몇개 간략히 소개해야하는데
그걸 소개하기 위해서는 시간이 좀 걸리기 때문에 소개를 생략하겠다.
다만 어떤 개념을 추가적으로 알아야되는지는 설명할테니 나중에 추후 포스팅을 참조하자.
우리가 이번에 알게될 용어는 비트(Bit), 바이트(byte), 무부호 타입(Unsigned), 자연수(Natural Number), 정수(Integer Number), 보수(Complement Number), 부동 소수점(Floating Point), 실수(Real Number) 이다.

여러분이 앞으로 C언어를 얼마나 쓰게될진 모르겠지만 C언어 같은 언어가 중요한게아니라
실제로 내부에 데이터가 저장되는 방식을 알아야 한다.
왜냐하면 저장 하는 방식은 모두 동일하기 때문이다.

컴퓨터는 실제 사람들이 쓰는 숫자를 알아먹지 못한다.
마치 한국어를 외국인이 못알아 먹듯이 말이다.
그래서 기계가 읽을 수 있게 번역되서 저장된다고 생각하면된다.
착한 사람인 우리들이 기계가 뭐라고 기억하는지 이해할 필요가 있고 그 이야기를 할 것이다.
저번에도 말했지만 C언어의 데이터형은 크게 4종류로 나뉠 수 있다.
무부호 타입(0을 포함한 자연수, 0을 포함한 양의 정수), 정수, 실수(소수), 진리값
이 순서대로 설명을 듣도록 하자.

중요한건 컴퓨터는 오직 1,0밖에 모른다, 즉 우리가 보는 모든 자료는 사실 2진수이다.
우리가 정수 10을 지정한게 컴퓨터에 정수 10으로 저장되는게 아니며,
실수 3.14를 지정한게 컴퓨터에 실수 3.14로 저장되는것도 아니다.
사실 컴퓨터는 해당 메모리의 값이 얼마인지 모른다.
바보기 때문이다. 그래서 우리는 해당 변수(메모리)가
크기(바이트)는 얼마이며, 타입은 무엇이다라고 알려주어야 그제서야 의미를 가진다는 것이다.
반대로 이야기하자면 메모리의 크기와 타입을 만약 속일 수 있다면
컴퓨터는 전혀 다른 값으로 인식되게 할 수 있다.
이를 이해해야만 앞으로 변수에 대해서 다룰 수 있다.

비트와 바이트에 대한 개념은 아주 쉽지만 간략하게 설명하도록 하겠다.
이제 무부호타입(0을 포함한 자연수)에 대해 알아보도록 하자.


무부호 타입이라는 것은 말그대로 부호가 없다는 뜻이다.
정확히 말하자면 모든 부호가 양수라는 뜻이다.
C언어에서 unsigned가 붙은 모든 정수를 의미한다.
프로그래밍에서(C언어에서가 아니다) 자연수를 지원하는 언어와 지원하지 않는 언어가 있다.
아니 그럼 자연수를 지원하지 않는다면 정수만 지원한다는 것이냐? 라는 것인데...
그렇다. 사실 필자도 자연수가 있어야하는지는 잘 모르겠지만...
관점의 차이, 혹은 필요의 차이라고 할 수 있다.
사실 default가 정수의 변형이 자연수이지만 자연스러운 흐름을 위해서
자연수부터 설명하려고 한다.
위에서 메모리는 무조건 1과 0으로만 저장된다고 하였다.
그래서 우리는 1과 0으로만 생각해야한다.
그래서 어떤 프로그래밍 언어던 모든 숫자는 2진수로 저장된다.
컴퓨터가 숫자를 읽는 방식은 (메모리와 데이터타입과 읽는 방식)의 조합이다.
여기서 말하는 데이터 타입은 자료형을 의미하고 읽는 방식은 서식일 것이다.
그러면 어떻게 저장하느냐?
먼저 숫자를 2진수로 바꾸는 방법부터 알아야할 것이다.

가장 간단한 방법은 계산기를 쓰는것이지만... 근데 계산기만 써도 될거 같은데?
여튼 우리가 학창시절에 배우던(혹은 지금 학창시절이거나) 2진수 변환을 보도록하자.

177을 계속 거듭 2로 나누고 나머지를 기록해둔다음 이를 역순으로 쓰면 2진수가 된다.
다만 나머지만 기록하는게 아니라 마지막 결과값 부터 시작해야한다.
즉 빨간색 값을 포함해야하면 이를 시작으로해서 나머지를 역순으로 올라가면 결과 값이 나온다.
그래서 최종적으로 값이 1011,0001이 된다.

우리는 실제로 변수를 255 같은 형태로 적지만 컴퓨터는 1111,1111로 받아들인다.
그리고 다시 읽을 때 타입이 없다면 그냥 1111,1111일 뿐이다.

그래서 우리는 이를 자연수라고 명시적으로 알려주면 컴퓨터는 이를 인식해서
255라고생각하고 거기에 맞춰서 작업을 수행하게된다.

여기서 unsinged char형은 1바이트라고 했다.
1바이트는 8비트이기 때문에 2진수 8자리를 나타낼 수 있다.
부족한자리는 0을 넣게된다.(아무작업을 하지 않는게 아니다.)
이러한 것을 zero pedding이라고 한다.
문제는 넘쳐날 경우이다.
윗그림에서 290은 2진수로 나타냈을 표현하려면 9비트가 필요하다.
그런데 unsigned char형은 8비트밖에 표현할 수 없다.
1바이트는 8비트이다.
이렇게 데이터 형만큼이 넘쳐난다면 데이터를 잘라버린다.
이러한 것을... 뭐라하는지 모르겠다. 뭐라부르지?
여튼 넘쳐나는건 잘라버리기 때문에 290을
unsigned char에 저장하면 이론상 34가된다.
정말로 그럴까? 한번 테스트 해서 한번 알아보자.
#include <stdio.h>
int main() {
char num = 290;
printf("%hhd",num);
return 0;
}

그만 알아보자.


우리는 수많은 0을 포함한 자연수,
즉 unsigned형이 어떻게 저장되는지 배웠다.
정수도 근본적으로는 unsinged와 같다.
하지만 생각해야할 점이 있다.
바로 음수를 어떻게 저장하는가이다.
컴퓨터는 0과 1밖에 못읽는데 음수를표기하려면
+, -를 표시해야한다는 근본적인 문제점이 생긴다.
이 문제점을 해결하기 위해서 등장한 것이 바로 보수표기법이다.

가령 10진법 체계에서 2의 10보수는 8이고 13의 10보수는 87이다.
반연 10진법 체계에서 2의 9보수는 7이고 13의 9보수는 86이다.
2진법 체계에도 당연히 2의 보수와 1의 보수가 있다.

컴퓨터는 2의 보수를 체택한다.
보수를 구하는 방법은 아주 아주 아주 쉽다.
제일 먼저 현재 적혀있는 숫자의 1과 0을 맞바꾼다.
가령 144의 보수는 110,0000인데 이는 원래 144의 0과 1을 바꾸면 나오는 수를 1의 보수라고한다.
그 다음에 거기서 1을 더하면 2의 보수가 된다.
보수가 왜 중요하냐면 컴퓨터는 각 자료형의 제일 상위의 1비트를 부호비트로 판단한다.
만약 이게 0이라면 양수로, 1이면 음수로 판단한다.
그래서 최상위가 1이다? 그러면 이 수가 "보수로 저장"되어있다고 판단하고
최상위 1비트를 제외하고 나머지 수를 재보수화 시킨다.
그 다음 -를 붙혀서 마무리한다.
조금더 세분화 해서 보도록 하자.

위의 숫자들을 우리가 입력하게되면 컴퓨터는 당연히 이를 2진수전환한다.
이 때 부호는 상관하지 않고 일단 2진수로 변환한다.

그 다음 이를2진수 전환한 다음 이를 char형 크기로 맞춰준다.
부족하면 0 패딩을, 넘치면 잘라버린다.

char형 크기로 맞춘다음 음수만 2의 보수화를 진행한다.
당연하지만 음수만 보수화 과정을 진행하며 양수와 0은 진행하지 않는다.
결국 이 값이 최종값이 된다.

우리가 다시 읽어 낼때는 이를 역과정을 한다고 생각하면된다.
또한 알아야 할점이 -255와 -257은 서로 다른 값이 나왔다는 것이다.
255는 unsigned char형은 표현이 가능한 범위였다.
이유는 8비트로 표현할 수 있는 마지막 수였기 때문이다.
하지만 -255는 보면 알겠지만 8비트만으로 표현할 수 없다.
왜냐하면 마지막 비트로 음수를 사용하기 때문이다.
그래서 char의 범위는 -128 부터 127 까지이다.
이를 저장할 경우 완전 다른 수가 출력되게 된다.
항상 음수의 표현범위가 하나 더 많은데 그 이유는 0 때문이다.
0은 부호가 +로 표현된다. 그래서 양수가 표현가능한 숫자를 하나 까먹게된다.

이제 왜 컴퓨터가 정수와, 자연수를 처리하는 방식이 다른걸 알겠는가?
그러면 이제 실수는 어떻게 표현하는 거지? 라는 궁금증이 스멀스멀 올라와야한다.
실수를 표현하는 방법도 알아보도록 하자.

실수를 표현하는 방법은 크게 고정소수점과 부동소수점이 있다.
부동소수점을 영어로 floating point라고 하며
변수 이름이 float이였던 이유도 바로 이 때문이다.
솔직히 이름은 조금 센스가 부족하다.
고정이나 부동이나 똑같이 움직이지 않는 것이라 생각할 확률이 농후하기 때문이다.
그런데 여기서 말하는 부동의 부는 부유하다, 즉 떠있다는 뜻이다.
부유해서 움직이므로 소수점이 움직인다는 뜻이다.
누가 이러한 네이밍 센스를 했는진 모르겠지만 개발자이니 그러려니하자.

부동소수점을 표현하기 위해서 3개의 부분을 사용한다.

부동 소수점을 나타내는 방식은 살작 복잡하다.
먼저 소수를 2진법으로 바꾸는 방법을 간략히 알아보자.

일단 2의 각승의 값이 뭔지 알아야 한다.
뭐 모르는 사람은 없겠지만 잠시 보고 머리좀 식히자.
그러면 어떻게 수를 표현해야할지 감이 잡히...나?

0.8125를 소수로 변환하는 예제는 위와 같다.
정수를 바꿀 때는 2로 나누었지만 소수를 바꿀때는 2를 곱해야한다.
그래서 0.8125는 1101로 변환된다는걸 알 수 있다.
그러면 예를 들어 이제 -118.625가 어떻게 저장되는지 보도록 하자.

부호부, 지수부, 가수부에 할당된 크기는 위와 같다.
float의 4바이트는 32비트이고 그 값들이 위처럼 알뜰 살뜰하게 나눠져 있다.
double의 8바이트는 64비트이고 그 값들이 위처럼 나눠져 있는걸 확인할 수 있다.
-118.625
먼저 -118.625에서 음수를 제거 한다 그리고 부호부는 1이된다.
118.625
나머지 118.625를 2진법 변환하면 1110110.101이 된다.
1110110.101
여기서 소수점을 맨앞의 1만남을때까지 이동시킨다.
1.110110101 * 2^6
여기서 앞에 구한 숫자의 소수는 가수부가 된다.(110110101)
가수부 - 110110101
여기서 지수부 6은 바로 지수부가 되지 않고 가중치와 연산을 한다.

왜냐하면 지수의 음수를 표현해야해서 그렇다. -100승을 어떻게 표현하나?
그래서 만약 지수부가 140이면 여기서 가중치인 127을 빼서 23승으로 인식한다.
가중치는 해당 자료형의 지수부 크기를 n이라고 하면 2^n/2이다.
float은 127이고 double은 1023이다.
지수부 - 10000101
그래서 6의 지수부는 6 + 127 = 133 이된다.
가수부는 나머지 부분은 0 패딩을 한다.

그래서 float에 저장할 경우 위처럼 저장되게 된다.


C언어에서 참과 거짓, 즉 진실과 거짓을 저장하는 값을 진리값이라고 한다.
true와 false로 나타내지며 당연히 true는 진실, false는 거짓이다.
C언어에서 숫자1은 진실로 판단된다. 즉 만약 진리값이 필요한 자리에
값 1에 해당하는 녀석을 넣으면 컴퓨터는 이 녀석을 진실이라고 판단한다.
그러면 가짜는 무엇인가?? 통념상으로 0이라고 생각하는데 이는 뭐 엄밀히 말하면은 아니다.
C언어에서는 1을 제외한 모든 녀석을 0이라고 판단한다.
'제로부터 시작하는 프로그래밍 > C' 카테고리의 다른 글
[CodeEater와 제로부터 시작하는 C언어] 5장 - 입력과 출력 (0) | 2019.12.01 |
---|---|
[CodeEater와 제로부터 시작하는 C언어] 4.2장 - 서식 지정자 (0) | 2019.11.30 |
[CodeEater와 제로부터 시작하는 C언어] 4장 - 변수와 자료형과 포맷 (0) | 2019.11.20 |
[CodeEater와 제로부터 시작하는 C언어] 3장 - 프로젝트 만들기/첫프로그램 (0) | 2019.11.12 |
[CodeEater와 제로부터 시작하는 C언어] 2장 - C언어란 무엇인가 (0) | 2019.11.11 |