본문 바로가기
c

c언어 3강 데이터 표현방식의 이해 (윤성우 열혈c)

by 킹차니 2021. 2. 19.

컴퓨터가 데이터를 표현하는 방식 


n진수에서 n은 데이터를 표현하는데 사용되는 기호의 개수를  의미한다. 2진수는 0과1로, 10 진수는 0,1,2,3,4,5,6,7,8,9로 16진수는 16개의 기호로 숫자를 표현한다.

 

10진수    :      2진수

    0        :         0

    1         :         1         (다음에 자릿수 증가)

    2        :         10

    3        :         11       (다음에 자릿수 증가)

    4        :         100

    5        :         101

 

 

16진수:   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F

 

10진수   :    16진수

    9        :        9

   10       :        A

   11        :        B

   12       :        C

   13       :        D

   14       :        E

   15       :        F     (자릿수 증가)

   16       :        10

             .

             .

             .

 153      :       99    

 154      :       9A

 155      :       9B

 

 

#데이터의 표현단위인 비트(bit)와 바이트(byte)

 

1bit는  2진수 값 하나를 저장할 수 있는 데이터의 최소단위

1byte = 8bit    1비트가 8개 모이면 1바이트가 된다.

 

 

@2진수를 10진수로 쉽게 바꾸는 방법:

 

2^5     2^4     2^3    2^2    2^1    2^0

  1         0         0       1        0        1     이므로

100101 =  32 + 4 + 1이다.

 

 

#8진수와 16진수를 이용한 데이터 표현

 

int main(void) {

    int num1 = 10;    // 특별한 표현이 없으면 10진수표현

    int num2 = 0xA;   // 0x로 시작하면 16진수로 인식

    int num3 = 012;   // 0으로 시작하면 8진수로 인식

    printf("num1=%d\n",num1);

    printf("num2=%d\n",num2);

    printf("num3=%d\n",num3);

}

출력결과 : 

num1=10

num2=10

num3=10

 

num2는 정수10이 16진수로 표현된것이고 num3는 정수10이 8진수로 표현된 것이다.

하지만 변수에 저장되는 값은 결국 10진 정수라는 사실을 기억하자. (출력결과를 보면 확인가능)(물론 컴퓨터는 내부적으로 2진수 형태로 값을 저장)

 

 

 

 

 

정수와 실수의 표현방식

 

컴퓨터는 정수, 실수, 문자 같은 데이터를  2진수로 어떻게 표현할까?

 

#정수의 표현방식

 

c언어는 보통 하나의 정수를 4바이트로 표현하지만 여기서는 편의를 위해 1바이트로 표현.

 

@ 정수의 가장 왼쪽에 존재하는 비트는 '부호비트'이다.

 

만약 저장하려는 값이 +1이라면 이렇게 표현한다.

 

[부호(+, - )의 표현]

          0                0  0  0  0  0  0  1      =    10진수 +1의 표현.

                          [  정수의 크기 표현  ]

 

정수를 표현하는데에 있어서 가장 왼쪽에 존재하는 비트는 부호를 표현하는데 사용된다. 양수라면 0 음수라면 1을 저장한다. 그래서 이 비트를 MSB(most significant bit)라고 부르며 가장 중요한 부호이다.

가장 왼쪽의 데이터를 제외한 나머지 비트들은 데이터의 크기를 나타낸다.

그렇다면 00000101은 MSB가 0이고 데이터가 0000101이므로 +5를 나타낸다.

 

@ 음의 정수를 표현하기

 

위의 00000001에서 MSB만 1로 바꾼다고 음수 값으로 바뀌지 않는다. 다음의 예시를 보자.

 

      0 0 0 0 0 1 0 1 (정수 +5)

+    1  0 0 0 0 1 0 1 (우리가 생각한 -5)

=    1  0 0 0 1 0 1  0  => 138

우리의 생각대로라면 (십진수로)+5와 -5를 더했으므로 0이나와야 하는데 결과는 138이 나온다.

 

+5를 기준으로 -5를 비트단위로 표현하는 방법은 다음과 같다.

1. 각각의 비트 별로 1의 보수를 취해준다.

    0 0 0 0 0 1 0 1  --> 1 1 1 1 1 0 1 0

2. 보수를 취해준 결과에 1을 더해준다.

    1 1 1 1 1 0 1 0 

+  0 0 0 0 0 0 0 1 

=  1 1 1 1 1 0 1 1   이것이 정수 -5이다.

 

이제 한번 -5의 비트단위 표현과 +5를 더해보자.

     0 0 0 0 0 1 0 1

+   1  1  1  1   1 0 1 1

= 1 0 0 0 0 0 0 0 0 (올림수가 버려져서 0)

 

이와 같은 방법을 2의 보수 법이라고 한다. 1의 보수를 취하고 1을 더하는 과정이 2의 보수를 구하는 과정이기 때문이다.

 

ex) 음의 정수 1 0 1 0 1 0 0 1 은 10진수로 얼마일까?

      step1) 1의 보수를 취한다.

                 1 0 1 0 1 0 0 1 -->  0 1 0 1 0 1 1 0

      step2) 1을 더해준다. 

                 0 1 0 1 0 1 1 0 + 0 0 0 0 0 0 0 1 = 0 1 0 1 0 1 1 1

                 0 1 0 1 0 1 1 1 은 10진수로 87이므로 따라서 답은 -87이다.

 

 

 

# 실수 표현 방식

 

컴퓨터는 적은 비트의 수를 가지고 넓은 범위의 실수를 표현하기 위해서 하나의 식을 정의한다.

 

부호의표현(MSB)

      |  [        e       ]    [         m         ]

      1 0 0 0 0 0 0 1   0 0 0 0 0 1 0 1

                  (1byte)                (1byte)

 

+- ( 1.m) * 2^(e-127)     :  실수의 표현을 위해 정의된 수식

 

위와 같은 방식으로 살수를 표현하면 넓은 범위의 실수를 표현할 수 있어 좋지만, 오차가 존재한다는 단점도 있다.

이렇듯 컴퓨터는 우리가 표현하고자하는 실수의 값을 정확하게 표현하는 것이 아니라 아주 가까운 근사치를 통해 표현한다. 이러한 오차를 부동 소수점 오차라고 한다. 아래의 예시를 통해서 오차를 확인하자.

 

int main(void) {

    int i;

    float num = 0.0;

    for(i=0;i<100;i++)

        num+=0.1;

    printf("0.1을 100번 더한 결과:%lf\n",num);

    return 0;

}

 

출력결과 :

0.1을 100번 더한 결과:10.000002

 0.1을 100번 더한 결과:10.000002

위의 예시를 보면 0.1을 백번 더했으므로 10이 나와야 하는데, 10.000002가 나온다.

 

 

비트 연산자 

 

연산자         

   &          비트단위로 AND연산을 한다. ex) num1&num2;

    |           비트단위로 OR연산을 한다.   ex) num1 | num2;

   ^           비트단위로 XOR연산을 한다. ex) num1 ^ num2;

   ~          단항 연산자로서 피연산자의 모든 비트를 반전시킨다. ex)~num; //num은 변화없음, 반전 결과만 반환

  <<         피연산자의 비트 열을 왼쪽으로 이동시킨다. ex)num<<2; //num은 변화 없음, 두 칸 왼쪽이동결과만 반환.

  >>         피연산자의 비트 열을 오른쪽으로 이동시킨다. ex)num>>2; //num은 변화 없음, 두 칸 오른쪽 이동결과만 반환.

 

 

&연산자 :비트단위AND

 

&연산은 두 개의 비트가 모두 1일 때 1을 반환한다.

0 & 0 : 0반환

0 & 1 : 0반환

1 & 0 : 0반환

1 & 1 : 1반환

아래는 비트AND 연산의 예시

int main(void) {

    int num1 = 15;     // 00000000 00000000 00000000 00001111

    int num2 = 20;     // 00000000 00000000 00000000 00010100

    int num3 = num1&num2;  //비트단위의 AND연산

    printf("AND연산의 결과:%d\n",num3);

    return 0;

}

출력결과:

AND연산의 결과:4

*int는 일반적으로 4바이트.

 

         00000000 00000000 00000000 00001111

&      00000000 00000000 00000000 00010100

결과: 00000000 00000000 00000000 00000100  이므로 결과는 4가 나온다.

 

 

|연산자 : 비트단위OR

 

 |(OR)연산은 두 개의 비트연산 중 한개라도 1이면 1을 반환하는 연산이다.

0 | 0  : 0반환

0 | 1  : 1반환

1 | 0  :  1반환

1 | 1  :  1반환

int main(void) {

    int num1 = 15;     // 00000000 00000000 00000000 00001111

    int num2 = 20;     // 00000000 00000000 00000000 00010100

    int num3 = num1|num2;  //비트단위의 OR연산

    printf("OR연산의 결과:%d\n",num3);

    return 0;

}

출력결과:

OR연산의 결과:31

         00000000 00000000 00000000 00001111

|연산  00000000 00000000 00000000 00010100

결과:  00000000 00000000 00000000 00011111 이므로 결과는 31이다.

 

 

 

^연산자: 비트단위 XOR

 

^연산은 두 개의 비트가 서로 다른 경우에 1을 반환하는 연산이다.

0 ^ 0 : 0반환

0 ^ 1 : 1반환

1 ^ 0 : 1반환

1 ^ 1 : 0반환

int main(void) {

    int num1 = 15;     // 00000000 00000000 00000000 00001111

    int num2 = 20;     // 00000000 00000000 00000000 00010100

    int num3 = num1^num2;  //비트단위의 XOR연산

    printf("XOR연산의 결과:%d\n",num3);

    return 0;

}

출력결과:

XOR연산의 결과:27

         00000000 00000000 0000000 00001111

^연산 00000000 00000000 0000000 00010100

결과:  00000000 00000000 0000000 00011011 이므로 결과는 27이다,

 

 

~연산자 : 비트단위 NOT

 

~연산은 비트를 0에서 1로, 1에서 0으로 반전시키기 때문에 보수연산 이라고도 불린다.

~0 : 1반환

~1 : 0반환

int main(void) {

    int num1 = 15;     // 00000000 00000000 00000000 00001111

    int num2 = ~num1;

    printf("NOT연산 결과:%d",num2);

}

출력결과:

NOT연산 결과:-16

00000000 00000000 00000000 00001111의 ~연산 결과: 11111111 11111111 11111111 11110000 이므로 -16이다.

 

 

<<연산자 : 비트의 왼쪽 이동(shift)

 

<<연산자는 두 개의 피연산자를 요구하며 다음의 의미를 갖는다.

num1 << num2 :  num1의 비트열을 num2칸씩 왼쪽으로 이동시킨 결과를 반환

8 << 2 : 정수8의 비트열을 2칸씩 왼쪽으로 이동시킨 결과를 반환  (왼쪽으로 밀려나는 비트는 소멸된다.)

                 00000000 00000000 00000000 00001111 -> 10진수 15

<<1칸 :      00000000 00000000 00000000 00011110 -> 10진수 30

<<2칸 :      00000000 00000000 00000000 00111100 -> 10진수 60

<<3칸 :      00000000 00000000 00000000 01111000 -> 10진수 120

 

위의 내용을 통해: "비트의 열을 왼쪽으로 N칸 이동시킬 때마다 정수의 값은 2^N배가 된다."

int main(void) {

    int num1 = 15;     // 00000000 00000000 00000000 00001111

    int result1 = num1<<1;

    int result2 = num1<<2;

    int result3 = num1<<3;

    

    printf("1칸 이동 결과:%d\n", result1);

    printf("2칸 이동 결과:%d\n", result2);

    printf("3칸 이동 결과:%d\n", result3);

    return 0;

}

출력결과:

1칸 이동 결과:30

2칸 이동 결과:60

3칸 이동 결과:120

 

 

>>연산자 : 비트의 오른쪽 이동(shift)

 

>>연산자는 <<연산자와 반대로 비트열을 오른쪽으로 이동시킨다.

 

>>연산자도 양수라면 밀려나는 오른쪽의 비트들은 소멸된다. 하지만 num1이 음수라면 이야기 달라진다.

예로  11111111 11111111 11111111 11110000 은 -16이다 이를 >>2를 진행하면

        00111111 11111111 11111111 11111100 (0이 채워진 경우) 이거나

        11111111  11111111 11111111 11111100 (1이 채워지는 경우) 이다. 이 결과는 cpu에 따라서 달라진다.

 

결과를 통해 보면 >>N칸 이동 할때 마다 정수의 값은 정수/2^N배가 된다.

int main(void) {

    int num1 = -16;     // 11111111 11111111 11111111 11110000

    int result1 = num1>>1;

    int result2 = num1>>2;

    int result3 = num1>>3;

    

    printf("오른쪽 1칸 이동 결과:%d\n", result1);

    printf("오른쪽 2칸 이동 결과:%d\n", result2);

    printf("오른쪽 3칸 이동 결과:%d\n", result3);

    return 0;

}

출력결과:

오른쪽 1칸 이동 결과:-8

오른쪽 2칸 이동 결과:-4

오른쪽 3칸 이동 결과:-2

내 컴퓨터는 1이 채워지는 cpu이다.


p.100의 연습문제1

int main(void) {

    int num,result;

    printf("정수하나를 입력하세요:");

    scanf("%d",&num);

    result = ~num+1;

    printf("당신이 입력한 정수의 부호를 바꾼 결과는: %d 입니다.\n",result);

}

 

연습문제2

3*8/4 를 비트연산자만 사용하여 결과 출력하기.

int main(void) {

    int num = 3;

    num = num<<3; // 8의 곱

    num = num>>2; // 4의 제

    printf("결과:%d\n",num);

    return  0;

}

출력결과:

결과:6