이번 Chapter에서는 진법에 대한 간단한 소개와 이를 기반으로 자료형에 대해, 컴퓨터의 정수와 실수 표현 방법에 대해 알아본다.
컴퓨터는 2진수를 기반으로 데이터를 표현하고 연산도 진행한다. 따라서 2진수를 이해해야 C언어를 보다 정확하게 이해할 수 있다.
2진수란? 2개의 기호를 이용해서 데이터를 표현하는 방식을 가리킨다.
그럼 10진수와 16진수는?
10진수는 10개의 기호, 16진수는 16개의 기호를 이용해 데이터를 표현하는 방식이다.
따라서, 2진수는 0과 1을 가지고,
10진수는 0~9라는 9개의 숫자를 가지고 수를 표현한다.
16진수의 경우 숫자 기호 10개 + 문자 6개(A~F)를 가지고 수를 표현한다. ex) 11 → A, 16 → 10
2진수는 컴퓨터가 0과 1로 데이터를 표현하니 왜 배워야하는지 이해가 되지만 왜 16진수도 알아야할까?
2진수 4개는 16진수 하나로 표현이 가능하기 때문에 많은 전자 및 컴퓨터 관련 서적들이 16진수를 사용한다. 따라서 16진수에도 익숙해질 필요가 있다!
비트(Bit)
란? 컴퓨터가 표현하는 데이터의 최소단위로, 2진수 값 하나를 저장할 수 있는 메모리의 크기를 뜻하는 단위다.
바이트(Byte)
는 비트 8개를 묶은 단위다.
C언어는 10진수 이외에 8진수, 16진수의 데이터 표현도 허용을 한다. 따라서 16진수나 8진수의 표현방식으로 변수에 값을 채울 수 있다.
10을 나타내는 각 진수별 방법을 아래 예시와 같이 나타낼 수 있다.
int num_10 = 10; // 특별한 선언이 없으면 10진수의 표현
int num_16 = 0xA; // 0x로 시작하면 16진수로 인식
int num_8 = 012; // 0으로 시작하면 8진수로 인식
그리고 간단한 예제를 하나 보자.
#include <stdio.h>
int main()
{
int num1 = 0xA7, num2 = 0X43;
int num3 = 032, num4 = 024;
printf("0xA7의 10진수 정수 값: %d \n", num1);
printf("0x43의 10진수 정수 값: %d \n", num2);
printf("032의 10진수 정수 값: %d \n", num3);
printf("024의 10진수 정수 값: %d \n", num4);
printf("%d-%d=%d \n", num1, num2, num1 - num2);
printf("%d+%d=%d \n\n", num3, num4, num3 + num4);
return 0;
}
우리는 컴퓨터가 데이터를 2진수로 표현한다는 것을 알고 있다.
정수, 실수, 그리고 문자와 같은 데이터를 어떻게 2진수로 표현할까?
정수의 가장 왼쪽에 존재하는 비트는 부호비트
다.
양수라면 0, 음수라면 1을 저장하여 부호를 표시하고 이를 MSB(Most Significant Bit)라고도 한다.
예를 들어 +1을 1바이트 메모리에 저장하려고 하면 아래 사진과 같이 가장 앞비트는 부호의 표현이고 나머지 7자리가 정수의 크기를 표현하는 것이다.
음의 정수를 표현할 때에는 2의 보수를 취해야 한다.
음수를 단순히 MSB만 1로 변경하면 되지~ 라고 생각한다면 간단한 예시로 +1과 -1을 더 했을 때 0이 되어야하는데 여기서 오류가 발생한다.
(00000001 + 10000001 = 10000010 → 0이 아니다!)
따라서 2의 보수법
이라는 과정을 거쳐 음수로 변환할 수 있는데 이 방법의 순서는 다음과 같다.
1. 모든 자리 수를 전환한다. (1 ↔ 0)
2. 1을 더한다.
이렇게 하면 같은 양의 양수와 음수를 더했을 때 0이 나온다.
컴퓨터가 실수를 표현하는 방식은 조금 복잡하다. 따라서 우린 개념적으로만 이해해보자.
우선 실수를 표현하는 방식은 2바이트를 가지고 앞부분 1바이트는 소수점 이상을 (MSB 포함), 그 다음 1바이트는 소수점 이하를 표현하는 것이다.
사실 이와 같은 방법대로 표현을 한다면 소수점 아래가 더 긴 많은 실수를 제대로 표현 할 수 없다.
따라서 오차가 존재하지만 (정밀도 ↓) 표현할 수 있는 값의 범위를 넓힌 표현방법이 등장하게 됐다.
이렇듯 실수를 표현하는데 있어서 오차가 존재하며 이러한 오차를 부동 소수점 오차
라 한다.
같은 값의 실수를 여러번 더해 오차가 발생하는지 확인하는 간단한 예제를 하나 보자.
#include <stdio.h>
int main()
{
int i;
float num = 0.0;
for (i = 0; i < 100; i++)
num += 0.1; // 이 연산을 총 100회 진행하게 됩니다.
printf("0.1을 100번 더한 결과: %f \n\n", num);
return 0;
}
이렇듯 컴퓨터는 실수를 표현할 때 근사치를 표현한다.
비트 연산자란? 비트 단위로 연산을 진행하는 연산자이다.
<<
연산자와 >>
연산자는 '비트 이동 연산자 (shift 연산자)'라 해서 비트 연산자와는 성향이 조금 다르다.
& 연산
은 두 개의 비트가 모두 1일 때 1을 반환하는 연산이다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num01 = 15; // 00000000 00000000 00000000 00001111
int num02 = 20; // 00000000 00000000 00000000 00010100
int num03 = num01 & num02; // 00000000 00000000 00000000 00000100 = 4
printf("AND 연산의 결과: %d \n\n", num03);
return 0;
}
> 예상 결과
4
같은 위치에 저장된 비트들 간 & 연산이 진행되어 정수 4가 반환된다.
| 연산
은 두 개의 비트 중 하나라도 1이면 1을 반환하는 연산이다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num01 = 15; // 00000000 00000000 00000000 00001111
int num02 = 20; // 00000000 00000000 00000000 00010100
int num003 = num01 | num02; // 00000000 00000000 00000000 00011111 = 31
printf("OR 연산의 결과: %d \n\n", num003);
return 0;
}
> 예상 결과
31
^ 연산
은 두 개의 비트가 서로 다른 경우에 1을 반환하는 연산이다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num01 = 15; // 00000000 00000000 00000000 00001111
int num02 = 20; // 00000000 00000000 00000000 00010100
int num0003 = num01 ^ num02; // 00000000 00000000 00000000 00011011 = 27
printf("XOR 연산의 결과: %d \n\n", num0003);
return 0;
}
> 예상 결과
27
~ 연산
은 비트를 0에서 1로, 1에서 0으로 반전시키기 때문에 보수연산이라고도 불린다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num01 = 15; // 00000000 00000000 00000000 00001111
int num001 = ~num01; // 11111111 11111111 11111111 11110000
printf("NOT 연산의 결과: %d \n\n", num001);
return 0;
}
> 예상 결과
-16
MSB 역시 반전이 되어 부호가 변경되었고, 2진수 숫자의 양수에서 음수 전환시 왜 +1을 해주는지 알 수 있다.
<< 연산자
는 두 개의 피연산자를 요구하며 다음의 의미를 갖는다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num01 = 15; // 00000000 00000000 00000000 00001111
int result1 = num01 << 1; // num의 비트 열을 왼쪽으로 1칸씩 이동
int result2 = num01 << 2; // num의 비트 열을 왼쪽으로 2칸씩 이동
int result3 = num01 << 3; // num의 비트 열을 왼쪽으로 3칸씩 이동
printf("1칸 이동 결과: %d \n", result1);
printf("2칸 이동 결과: %d \n", result2);
printf("3칸 이동 결과: %d \n\n", result3);
return 0;
}
> 예상 결과
00000000 00000000 00000000 00011110 = 30
00000000 00000000 00000000 00111100 = 60
00000000 00000000 00000000 01111000 = 120
보다시피 1칸씩 왼쪽으로 이동하면 값이 2배씩 늘어난다. num << n 일 경우 가 되는 것이다.
상황에 따라서는 곱셈과 나눗셈 연산은 비트의 이동 연산으로 대체할 수 있으며 이는 성능 향상으로 이어진다.⭐
>> 연산자
는 << 연산자
의 반대 방향인 오른쪽으로 비트 열을 이동시켜 결과를 반환한다.
앞에 빈 부분은 CPU에 따라 0으로 채워질지 1로 채워지리 달라진다.
예제를 통해 연산 결과를 확인해보자.
#include <stdio.h>
int main()
{
int num002 = -16; // 11111111 11111111 11111111 11110000
printf("2칸 오른쪽 이동의 결과: %d \n", num001 >> 2);
printf("3칸 오른쪽 이동의 결과: %d \n\n", num002 >> 3);
return 0;
}
> 예상 결과
// 1로 채워질 경우
11111111 11111111 11111111 11111100 = -4
11111111 11111111 11111111 11111110 = -2
// 0으로 채워질 경우
00111111 11111111 11111111 11111100
00011111 11111111 11111111 11111110
내 CPU는 부호비트를 유지하는 시스템이었다!
<Review>
나는 사무실과 집에 있는 데스크탑으로 왔다갔다 하며 c언어를 공부하고 있는데
원래 공부하던 자료들이 다 집 컴퓨터에 있어서 github에 올리고 사무실 컴퓨터로 받아서 사용하는데
이 코드들이 vs에서 ctrl+F5를 눌러 실행하려고 하면 msb3191 오류라고 하면서 계속 같은 이름의 파일과 디렉터리가 있어 디버그 파일을 생성을 못한다고 떴다!
이거를 해결하는데 1시간 넘게 걸려 딜레이 됐지만
해결 방법은!
파일명을 바꾸면 되는 간단한 문제였다 ^^
예를 들어 내가 Chapter4에 예제들을 적어놓은 파일명이 'Chapter4.c' 라면 이걸 'Chapter4ex.c' 등의 이름으로 변경하여 다시 실행하면 되는 것이다...
사실 이렇게 되면 집 로컬에서 작업할 때와 파일명이 달라져 좀 난감하긴한데,,,
사무실에서 작업했을 때 파일명 규칙을 정해 git에 올리면 되지 않을까 싶다,,,^^
사실 완벽한 솔루션은 아닌거 같지만,,, 우선은 공부 중이니 이렇게 해결하는 걸로 위안을 삼아야겠다 ㅎㅎㅎ
오늘도 알찬 공부 끝!