본문 바로가기
👩‍💻Developer/Language

[C] C에서 숫자란?

by 블루누들 2022. 12. 14.

* 해당 글은 학과 수업으로 배운 내용과 코드가 포함되어 있으며 개인적 공부 목적으로 업로드하였습니다.

 

C0/C1에서는 int는 32 byte이었는데 C에서의 int size는 오랜 시간 변해왔다.

 

현재 C에서는 int size는 compiler에 의해 결정된다.

이를 우리는 implementation-defined라고 부를 수 있다.

단, 이는 undefined behavior와는 다른 개념이다. 

 

C에서는 다양한 integer type이 존재한다.

long : int보다 큰 타입의 integers

short: int보다 작은 타입의 integers

char: short보다 작은 타입의 integers (1 byte)

 

C는 각 integer별로 unsigned variants를 제공한다. 

-같은 bits

unsigned long

unsigned int

unsigned short

unsigned char

 

이때에 size_t는 memory address의 size를 의미한다

이를 표로 정리해보자면

signed unsigned c99 constraints Today's size
signed char unsigned char exactly 1 byte 8 bits
short unsigned short range at least(-2^15, 2^15) 16 bits
int unsigned int range at least(-2^15, 2^15) 32 bits
long unsigned long range at least(-2^31, 2^31) 64 bits

 

Integer Casts

우리는 다양한 숫자들 사이에서 casting을 반복할 수 있다.

-Literal number는 항상 int type을 가진다. compiler가 필요할 때 implicit casts를 진행한다.

long x = (long) 3;

 

하지만 이는 간혹 예기치 못한 결과를 가져올 수도 있다. 

ex) long x = 1 <<40는 undefined behavior가 된다.

long x = (long) (1 <<40);

-1은 32 bits라서 40 positions으로 shift할 수 없다.

-> long x = ((long)1) << 40;

 

Casting Rules

새로운 type이 이전 type의 value를 나타내는데에 문제 없다면 그 value는 지켜져야 한다.

Ex 1)

signed char x = 3; // x is 3 (0x03)
unsigned char y = (unsigned char)x; //y is 3 (0x03)

Ex 2)

signed char x = 3;   //x is 3 (=0x03)
unsigned int y = (unsigned int)x;   //y is 3 (0x00000003)

Ex 3)

signed char x = -3;    //x is -3 (=0xFD)
int y = (int)x;     //y is -3 (0xFFFFFFFD)

Ex 4)

unsigned char x = 253;   //x is 253 (=0xFD)
unsigned int y = (unsigned int)x;  // y is 253 (=0x000000FD)

Ex 5)

int x = -3;    //x is -3 (0xFFFFFFFD)
signed char y = (signed char) x;  //y is -3 (0xFD)

 

만약 새로운 타입이 이전 타입의 value를 똑같이 나타낼 수 없지만 새로운 타입이 unsigned 라면 두가지 경우로 나뉜다.

- 새로운 타입이 작거나 같으면 least significant bits만 남겨진다.

ex)

int x = INT_MAX;  //(0x7FFFFFF)
unsigned char y = (unsigned char)x; //(0xFF)

signed char x = -3;   //(=0xFD)
unsigned char y = (unsigned char)x;  //(0xFD)

만약 새로운 탕비이 더 크다면 bits는 sign extended 된다.

signed char x = -3;  //(0xFD)
unsigned int y = (unsigned int)x;  //(0xFFFFFFFD)

마지막으로 새로운 타입이 value를 그대로 나타낼 수 없지만 signed의 형태로 cast된다면 결과는 implementation defined가 된다.

 

ex)

int x = INT_MAX;   
signed char y = (signed char)x;

int x = -241;
signed char y = (signed char)x;

Casting을 요약해보자면

Fixed-size Integers

bit patterns 역시 C에서 마찬가지로 작동하게 할 필요가 있다.

<stdint.h> header file이 정해진 size의 integer type을 제공하고 있다.

Fixed size signed signed equivalent unsigned equivalent fixed size unsigned
int8_t signed char unsigned char uint8_t
int16_t short unsigned short uint16_t
int32_t int unsigned int uint32_t
int64_t long unsigned long uint64_t

 

Float

float은 floating point numbers를 나타낸다.

ex) float x = 0.1;

float y = 2.0235E-27;

 

float과 int는 같은 bit를 사용하지만 float이 더 넓은 range를 포함한다.

-단 어떠한 숫자는 decimal 표현이 어려울 수 있다.

또한 이는 rounding error를 일으킬 수 있다.

 

ex)

#include <math.h>
#define PI 3.14159265

float x = sin(PI); //sin(pi)는 수학에서는 0이지만 0.0과는 다르다

float y = (10E20/10E10) * 10E10;  //(10^20/10^10) * 10^10
//이는 compiler에 따라 값이 달라질 수도 있다.

for(float res = 0.0; res!=50; res+=0.1)
  printf("res=%n", res);
  
//우리는 이 반복문이 50번의 iteration을 거친 후 멈추길 예쌍하지만 계속해서 돈다
// 그 이유는 0.1 이 binary에서 periodic number이기 때문이다.

이에 따라 bit을 더 추가한다고 해서 해결되지는 않는다.

 

Union/Enum types

 

// 0 = winter, 1=spring, 2=summer, 3 =Fall

int today = 3;
if(today==0)
  printf("snow/n");
elseif (today==3)
  printf("leaves!\n");
else
  printf("sun!\n");

4개의 int로 나눠서 생각하는 것은 memory 낭비 문제가 있어보인다. 

 

이때 enum type은

-프로그래머가 직접 연상기호 값을 고를 수 있게 해준다

-compiler가 이를 어떻게 implement할지 결정하도록 한다.

enum season {WINTER, SPRING, SUMMER, FALL};

enum season today = FALL:
if (today==WINTER)
  printf("snow\n");
else if (today==FALL)
  printf("leaves!\n");
else
 printf("sun!\n");

 

Switch statement

switch statement는 numerical value에서  if-else를 대체할 수 있는 alternative 중 하나이다.

enum season {WINTER, SPRING, SUMMER, FALL};

enum season today = FALL;

switch(today){
  case WINTER:
    printf("snow\n");
    break;
  case FALL:
    printf("leaves!\n");
    break;
  default:
    printf("sun!\n");

각각의 값들은 case로 다뤄지며 다음 break이 오기전까지 혹은 switch statement가 끝날때까지 반복된다.

-default는 남은 value들의 케이스들을 다룬다.

만약 break가 빠진다면 하나의 케이스에 해당해도 다음 케이스를 지속해서 compile한다. 

 

leaves에 int type만 있는 binary tree type을 정의해보자

이 tree는 : 1) 두 children을 향하는ㄴ pointer의 inner node일 수 도 있고

2) int data를 가진 잎을 가질 수도 있고

3) empty tree일 수도 있다.

 

enum nodekind = {INNER, LEAF, EMPTY};

struct ltree{
  enum nodekind kind;
  int data;
  leafytree* left;
  leafytree* right;
};
typedef struct ltree leafytree;

이는 메모리를 낭비하고 있음을 볼 수 있다. 

이때 생각해볼 것이 union type이다. 

 

Union type

union type은 같은 space를 다른 방향으로 나눠 사용할 수 있도록 한다. 

enum nodekind = {INNER, LEAF, EMPTY};

struct innernode{
 leafytree* left;
 leafytree* right;
};  //innernode는 두가지 pointer를 소유한다.

union nodecontent{
  int data;
  struct innernode node;
};

struct ltree{
  enum nodekind kind;
  union nodecontent content;
};
typedef struct ltree leafytree;

이를 활용해 tree를 만들어보자

leafytree* T = malloc(sizeof(leafytree));
T -> kind = INNER;
T -> content.node.left = malloc(sizeof(leafytree)); //pointer를 follow하는게 아니면 dot notation을 활용해야한다
T -> content.node.left->kind = EMPTY;
T -> content.node.right = malloc(sizeof(leafytree));
T -> content.node.right->kind = LEAF;
T -> content.node.right->content.data = 42;

그림으로 나타내면 아래와 같다.

이또한 switch를 활용해 작성할수있다.

int add_tree(leafytree* T){
  int n = 0;
  
  switch(T->kind){
   case INNER:
     n+= add_tree(T->content.node.left);
     n+= add_tree(T->content.node.right);
     break;
   case LEAF:
     n = T->content.data;
     break;
   
   default:
     n=0;
   }
  return n;
}

'👩‍💻Developer > Language' 카테고리의 다른 글

[C] Search in Graph  (0) 2022.12.16
[C] Graph In C  (0) 2022.12.15
[C]Bytecode In C  (1) 2022.12.15
[C/C++] C언어의 Memory model  (1) 2022.11.29
[C] C언어에 대해 알아보자  (0) 2022.11.25

댓글