배움 저장소
홍정모의 따라하며 배우는 C언어 3 본문
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 1100은 124를 나타낸다. 이때 0000 0000과 1111 1111은 특수한 용도로 사용한다. 따라서 사용할 수 있는 수는 0000 0001~1111 1110이다. 이는 1~254이고 총 254개 경우를 나타낼 수 있다. 방금 예는 unsigned의 예제이다. 이렇게 하면 맨 앞 숫자를 희생하지 않고도 unsigned와 signed 모두를 사용할 수 있다. signed에 활용하려면 이 숫자에 254/2인 127을 빼주면 된다. 그러면 나타낼 수 있는 경우는 -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));
}
'Programming Language > C' 카테고리의 다른 글
홍정모의 따라하며 배우는 C언어 8 문자 입출력과 유효성 검증 (0) | 2021.11.15 |
---|---|
홍정모의 따라하며 배우는 C언어 7 분기 (0) | 2021.11.15 |
홍정모의 따라하며 배우는 C언어 6 반복문 (0) | 2021.11.12 |
홍정모의 따라하며 배우는 C언어 5 연산자, 표현식, 문장 (0) | 2021.11.11 |
홍정모의 따라하며 배우는 C언어 4 문자열과 형식지정자 (0) | 2021.11.10 |