배움 저장소

홍정모의 따라하며 배우는 C언어 3 본문

Programming Language/C

홍정모의 따라하며 배우는 C언어 3

시옷지읏 2021. 11. 9. 20:40

3.10 문자형

#include <stdio.h>
int main()
{
	char c = 'A';
	char d = 65;
    
    printf("%c %hhd\n", c,c); // -> A 65 
    printf("%c %hhd\n", d,d); // -> A 65
    
    printf("%c %hhd\n", ',d); // -> A 65
    printf("%c %hhd\n", d,d); // -> A 65
   
    printf("\a"); // -> beef alarm
    printf("\07"); // -> 8진수 7 ( character 'a'와 동일)
    printf("\x7"); // -> 16진수 7
    
    
    float salary;
    printf("$______\b\b\b\b\b\b");// $____	
    scanf("%f", &salary);	      //  ^ 해당 위치에서 _(undersocre)를 지워가며 입력된다.
}

%hhd?

%hhd 는 signed char 타입의 정수를 출력할 때 사용됩니다. 즉 1byte 크기의 정수를 출력할 때 사용합니다.

%d 👉 int. 즉 4byte(32bit) 로 표현한 정수를 출력하고자 할 때.

%hhd 👉 signed char. 즉 1byte(8bit) 로 표현한 정수를 출력하고자 할 때.

이런 차이가 있습니다. 

예를 들어 숫자 5 를 %d 로 출력한다면  내부적으론 32자리 비트의 0000 0000 0000 0000 0000 0000 0000 0101 로 처리되고, %hhd로 출력한다면 내부적으론  8자리 비트의 0000 0101 로 처리 됩니다. 둘 다 십진수로 변환하면  5인건 똑같기 때문에 %d 로 실행해도 결과가 동일한 것입니다. 

( 덧붙여서 char도 사실 내부적으로는 signed char 인, 즉 1byte 크기의 정수로 저장이 됩니다. char 변수를 출력할 땐 이 정수 값을 아스키 코드와 일치하는 문자로 출력해주는 것 뿐입니다.)

 

 

3.11 부동소수점형

Floating Point

 123을 1.23 * 10^2로 표현할 수 있다. 여기서 10 대신에 2의 배수를 활용하는 방법이 있는데 이를 Normalized Significand라고 부른다. Floating Point는 컴퓨터 내부에서 다음과 같은 형태로 저장된다.

(1.xxxx * 2^x) Normalized Significand

 다음 그림에서 Fraction 부분은 1.23 역할을 하고 exponent가 10^2 역할을 한다. 단 10 대신 2의 지수를 사용한다는 점이 다르다.

exponent 부분은 2의 배수 값으로 뒤에있는 값 fraction (1+소수 값)을 얼마나 크게 만들지를 결정한다.

exponent 부분의 0111 1100124를 나타낸다. 이때 0000 00001111 1111은 특수한 용도로 사용한다. 따라서 사용할 수 있는 수는 0000 0001~1111 1110이다. 이는 1~254이고 총 254개 경우를 나타낼 수 있다. 방금 예는 unsigned의 예제이다. 이렇게 하면 맨 앞 숫자를 희생하지 않고도 unsigned와 signed 모두를 사용할 수 있다. signed에 활용하려면 이 숫자에 254/2127을 빼주면 된다. 그러면 나타낼 수 있는 경우는 -126~127이 된다.

 

fraction의 맨 앞 자리는 1/2 그 다음은 1/4..... 마지막 자리는 2^(-23)이 된다. 2의 음의 지수를 내림차순으로 정렬해놓았다.

따라서 위의 그림을 식으로 나타내면 다음과 같다

 

sign = +

exponent = 124

fraction = 1/4

 

이때 exponent는 signed를 사용하므로 -127을 해주어야 하고 fraction은 항상 맨 앞에 1을 더해주어 1.xx로 표현해주어야한다. 따라서 

sign(+) 

exponent part = 2^(124-127)

fraction part = 1 + 1/4

= 0.125 * 0.125

= 0.15625

 

부동 소수점을 사용하는 이유는?

부동소수점을 이용하면 유효숫자가 줄어들지만 넓은 범위의 숫자를 사용할 수 있다. 숫자가 표현될 때 유효숫자 이외의 숫자는 오차값을 가질 수 있다.

4byte 부동소수점수 범위는 -3.4 *  E38 ~ 3.4  * E38  (10진수 유효숫자는 6개)

4byte 정수 범위는            -2.14 * E9  ~ 2.14 * E9 (10진수 유효숫자는 10개)

위 정수 범위는 다음과 같다. (-2,147,483,648 ~ 2,147,438,647)

 

유효숫자가 6개인 이유

더보기

정진호2021.03.12 PM 19:51

부동소수점 형태로 저장하면 

float로 저장하는 경우 4바이트 겠죠?

근데 컴퓨터에 저장될때 2진 소수로 바뀌죠? 총 32 비트겠죠?

그럼 1비트는 sign에쓰고 8비트는 지수에 쓰고 나머지 23비트를 가수에쓰겠죠?

23비트를 쓴단 말은 가수 첫 비트가 2^-1 부터 시작하니까 2^-23 까지 쓴단 말이겠죠?

그럼 오차는 어디서부터 발생할까요?

2^-24번째 비트부터 생겨나겠죠?

그럼 '최대'오차가 얼마일까요? 2^-24 X 1 + 2^-25 X 1 + 2^-26 X 1 + ... 이거겠죠?

이거 수능공부할때 많이 봤죠? 무한등비급수의 합이죠?

다 더하면 얼마에요? 2^-23 이죠?

2^-23 은 10진수로 0.0000001192 죠?

그래서 float의 정밀도가 7자리입니다(소수점이하 6자리)

 

각 타입이 몇 byte를 사용하는지 출력해보면 다음과 같다.

int main()
{
	// gcc
	printf("%u\n", sizeof(float));         -> 4
	printf("%u\n", sizeof(double));        -> 8
	printf("%u\n", sizeof(long double));   -> 12

	//msvc
	printf("%u\n", sizeof(float));         -> 4
	printf("%u\n", sizeof(double));        -> 8
	printf("%u\n", sizeof(long double));   -> 8
}

 

MSVC 컴파일러와 gcc 컴파일러의 출력값이 다르게 나온다. 

MSVC에서 double과 long double 모두 8byte이다.

GCC에서 double은 8byte long double은 12byte이다.

// How to use 1
int i = 3;
float f = 3.0f; // or 3.f;
double d = 3.0; // or 3.;

// How to use 2
float f2 = 123.456f;
double d2 = 123.456;

// truncated
float f3= 123.456;      // assign 8byte value to 4byte value
double d3 = 123.456f; 	// assign 4byte value to 8byte value


// How to use 3
float f4 = 1.234E10f; // 1.234 * 10^10;

float f5 = 0xb.aP1 // '0x' means hexadecimal 
                   // P is same as E in decimal
double d5 = 1.0625e0;
  
// Print decimal
printf("%f %F %e %E\n", f, f, f, f); // "e,E" for scientific notation
printf("%f %F %e %E\n", d, d, d, d);  

// Print hexadecimal
printf("%a %A\n", f5, f5);
printf("%a %A\n", d5, d5);

 

3.12 부동소수점의 한계

// Round off Error1
float a = 1.0e20f + 1.0f;
float b = a - 1.0e20f;    // b should be 1.0f
printf("%f\n", b);        // but printed 0.0f


// Round off Error2
a = 0;
for(int i=0; i<100; ++i)
{
	a = a + 0.01f; 
}                   // a should be 1.0f
printf("%f\n", a);  // but printed 0.99f

 첫 번째 결과값이 0이 나온다. float 자료형을 사용할 때 숫자 크기가 많이 차이나는 연산을 하지 말자.

 두 번 째 결과값이 0.99가 나오는 이유는 float 타입이 2진수로 fraction을 저장하기 때문에 0.01을 표현하지 못한다. 0.01은 내부적으로 0.999....776이라 계속 연산하다 보면 오차값이 생긴다.

 

 float 타입과 double 타입은 max 값을 넘어서 overflow 될 때 INF를 출력한다.

                                    min 값 보다 작아져 underflow 일때 0을 출력한다. (Subnormal:숫자가 사라짐)

 

3.13 불리언형

- bool 타입을 사용하려면 stdbool 헤더를 포함시켜야 한다. 초창기 c는 bool 타입을 지원하지 않았다. _Bool을 사용해야 한다.

- 흥미롭게도 bool 타입의 사이즈는 1bit=1/8 byte가 아닌 1byte인데 데이터를 배정받기 위한 최소 사이즈가 1byte이기 때문이다.

- 컴퓨터가 bool type을 구분할 때는 내부적으로 false인지 false가 아닌지를 판단한다.

#include <stdio.h>
#include <stdbool.h>

int main()
{
	printf("%u\n", sizeof(_Bool)); // -> 1byte
    _Bool b1;
    bool b2; // stdbool.h
}

 

3.14 복소수형

보통 직접 만들어 씀이 일반적이나 라이브러리가 간단하게 지원하고 있다.

#include <stdio.h>

#include <complex.h> // 복소수를 사용하기 위해 필요함

int main()
{
    // MSVC
    _Dcomplex z;
    z._val[0] = 1.0;
    z._val[1] = 1.0;
    
    // GCC
    double _Complex z = 1 + 2i;
    z = 1/z;
    printf("1 / (1.0 + 2.0i) = %.1f%+.1fi\n", creal(z), cimag(z));

}
Comments