본문 바로가기

공부/C언어

C언어 - (3) : 자료형 별 데이터의 표현 방식 / 비트 연산자

비트(bit)와 바이트(byte)

1bit는 2진수의 한자리 수를 의미하고, 1byte = 8bit 이다.

컴퓨터 메모리의 주소 값은 1byte당 하나의 주소가 할당되어 있다.

따라서, 1byte에는 $\sum_{i=0}^{7} 2^{i} = 2^0 + 2^1 + \text{...} + 2^7 = 2^8-1 = 255$까지의 수를 저장할 수 있다.

 

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

//base8 & base16
#include <stdio.h>
int main(void){

    // 특별한 선언이 없으면 10진수
    int num1=0xA7, num2=0x43; // 0x로 시작하면 16진수
    int num3=032, num4=024; // 0으로 시작하면 8진수
    
    printf("0xA7의 10진수 정수 값: %d \n", num1); // 16*A(10) + 7 = 167
    printf("0x43의 10진수 정수 값: %d \n", num2); // 16*4 + 3 = 67
    printf(" 032의 10진수 정수 값: %d \n", num3); // 8*3 + 2 = 26
    printf(" 024의 10진수 정수 값: %d \n", num4); // 8*2 + 4 = 20

    printf("%d-%d=%d \n", num1, num2, num1-num2); // 167-67 = 100
    printf("%d+%d=%d \n", num3, num4, num3+num4); // 26+20 = 46

    return 0;
}

 

정수와 실수의 표현 방식

1byte에서 가장 왼쪽의 bit를 MSB(Most Significant Bit)라고 부르며, 부호를 나타내는 역할을 한다.

MSB를 제외한 나머지 bit는 수의 크기를 나타내는 역할을 한다.

ex) 00000101 : 5 / 10000101 : -5

음의 정수 표현 방식

  1. 00000101 = 5
  2. 보수를 취한다 (2진수에서 각 자리수를 반대로 바꿈)
    • 11111010
  3. 1을 더한다
    • 11111010 $\rightarrow$ 11111011 = -5

00000101 + 11111011 = (1)00000000 = 0 이므로 적절한 표현법

실수의 표현 방식

정수를 표현하는 방식과 유사하게 실수를 표현하게 되면, 표현할 수 있는 실수의 개수에 대한 한계가 너무 커서 제대로 표현할 수가 없다.

https://www.tcpschool.com/c/c_refer_floatingPointNumber#google_vignette

그렇기 때문에 실수에 대한 정확도를 조금 희생해서 표현할 수 있는 실수의 범위를 늘리는 방법을 채택하는데, 이를 floating point(부동소수점)방식이라고 한다.

https://donghwada.tistory.com/entry/%EC%8B%A4%EC%88%98%EC%9D%98-%ED%91%9C%ED%98%84%EC%A0%80%EC%9E%A5-%EB%B0%A9%EC%8B%9D

컴퓨터는 이런 방식으로 실수를 표현하기 때문에, 완벽한 값으로 실수를 표현할 수 없게 된다.

// 실수 표현
#include <stdio.h>

int main(void){
    int i;
    float num=0.0;

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

    printf("0.1을 100번 더한 결과 : %f \n", num);
    // 0.1을 100번 더한 결과 : 10.000002 
    return 0;
}

`fixed point`와 `floating point`의 차이를 조금 더 명확하게 구분하기 위해 간단한 예시를 들어보자.

 

`23.625`에 대해 `fixed point`를 사용해 이진 변환을 해보면,

  1. 정수부
    • $23_{10} = 10111_{2}$
  2. 소수부
    • $0.625_{10} = 0.101_{2}$
    • 소수부에 대한 이진 변환은 정수부와 반대로, 2를 곱해준 값의 정수부를 차례로 이어 붙이면 된다(정수부와 같은 원리)
      • 정수부 : $x/2$
      • 소수부 : $x/2^{-1}$
  3. 정수부와 소수부 연결
    • $23.625_{10} = 10111.101_{2}$

다음으로, `floating point`를 사용한 이진 변환은 다음과 같다.

  1. `fixed point`와 동일한 방식의 이진 변환
    • $23.625_{10} = 10111.101_{2}$
  2. Normalize(지수부 + 가수부)
    • $10111.101_{2} = 1.0111101 \times 2^{4}$
      • 지수부(exponent) = 4 + 127(exponent bias) = 131 $= 10000011_{2}$
      • 가수부(mantissa) = $0111101_{2}$
  3. 지수부와 가수부 연결
    • $23.625_{10} = 0(\text{sign bit})\;10000011(\text{exponent bit})\; 0111101(\text{mantissa bit})$

정리해보면, `fixed point`의 경우에는 이진 변환을 그대로 각 자리수에 채워 넣는 방식이고 `floating point`는 지수 변환을 한번 거친 후에  각 지수부, 가수부에 값을 채우는 방식이다.

 

따라서, `floating point`의 경우가 `fixed point`보다 표현력이 풍부하고 메모리 효율적인 것이다.


비트 연산자

비트 단위로 연산을 실행하도록 하는 연산자

연산자 연산자의 기능 결합방향
& 비트 단위로 AND 연산을 한다
(num1 & num2;)
$\rightarrow$
| 비트 단위로 OR 연산을 한다
(num1 | num2;)
$\rightarrow$
^ 비트 단위로 XOR 연산을 한다
(num1 ^ num2;)
$\rightarrow$
~ 단항 연산자로서 피연산자의 모든 비트를 반전시킨다. $\leftarrow$
<< 피연산자의 비트 열을 왼쪽으로 이동시킨다.
(num<<2;) - 왼쪽으로 두칸 이동
$\rightarrow$
>> 피연산자의 비트 열을 오른쪽으로 이동시킨다.
(num>>2;) - 오른쪽으로 두칸 이동
$\rightarrow$
// 비트 단위 연산자
#include <stdio.h>

int main(void){
    int num1=15; // 비트 단위 : 00000000 00000000 00000000 00001111
    int num2=20; // 비트 단위 : 00000000 00000000 00000000 00010100
    // AND 연산자(&)
    int num3 = num1 & num2; // 비트 단위 : 00000000 00000000 00000000 00000100 = 4
    printf("AND의 연산 결과 : %d \n", num3);

    // OR 연산자(|)
    int num4 = num1|num2; //비트 단위 : 00000000 00000000 00000000 00011111 = 16+8+4+2+1 = 31
    printf("OR의 연산 결과 : %d \n", num4);

    // XOR 연산자(^)
    int num5 = num1^num2; //비트 단위 : 00000000 00000000 00000000 00011011 = 16+8+2+1 = 27
    printf("XOR의 연산 결과 : %d \n", num5);

    // NOT 연산자(~)
    int num6 = ~num1; /*비트 단위 : 11111111 11111111 11111111 11110000 -1 = 11111111 11111111 11111111 11101111 
    = 00000000 00000000 00000000 00010000 = 16의 보수 => -16*/ 
    printf("NOT 연산의 결과 : %d \n", num6);

    // Shift 연산자(left : <<)
    int num7 = num1<<1; // 비트 단위 : 00000000 00000000 00000000 00001111 -> 00000000 00000000 00000000 00011110 = 2+4+8+16 = 30
    int num8 = num1<<2; // 비트 단위 : 00000000 00000000 00000000 00001111 -> 00000000 00000000 00000000 00111100 = 4+8+16+32 = 60

    printf("1칸 왼쪽 이동 결과 : %d \n", num7);
    printf("2칸 왼쪽 이동 결과 : %d \n", num8);

    // Shift 연산자(right : >>)

    /* CPU에 따라서 연산 결과가 달라짐
        ex) -16>>2
        - 비는 자리에 0이 채워지는 경우 : -16 = 11111111 11111111 11111111 11110000 -> 0011111111 11111111 11111111 11111100 (양수)
        - 비는 자리아 1이 채워지는 경우 : -16 = 11111111 11111111 11111111 11110000 -> 1111111111 11111111 11111111 11111100 (음수)
    */
    int num = -16;
    int num9 = num>>2; // -16 = 11111111 11111111 11111111 11110000 -> ??111111 11111111 11111111 1111100 = -4 => 1로 채워짐
    int num10 = num>>3; // -16 = 11111111 11111111 11111111 11110000 -> ???11111 11111111 11111111 1111110 = -2 => 1로 채워짐

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

    return 0;
}

 

'공부 > C언어' 카테고리의 다른 글

C언어 - (6) : 반복문  (0) 2024.10.07
C언어 - (5) : printf / scanf  (3) 2024.10.06
C언어 - (4) : 자료형/형 변환  (3) 2024.10.03
C언어 - (2) : 변수 / 연산자  (0) 2024.09.09
C언어 - (1)  (0) 2024.09.05