[C] 7. 형 변환(Type Casting)

Wonder_Land🛕·2022년 7월 19일
0

[C]

목록 보기
7/18
post-thumbnail
  1. 부동 소수점
  2. 형 변환(Type Casting)
  3. Q&A
  4. 마치며

C언어에서는 각 변수들에는 고유한 형(Type)이 있습니다.

int a로 선언된 변수 a는 int형,
char b로 선언된 변수 b는 char형
이죠.

그런데 가끔 형(Type)이 다른 변수끼리 대입하는 연산이 필요할 때도 있습니다.

예를 들어, double형 변수의 값을 int에 대입하거나 등등이죠.

하지만 이러한 연산은 모두 불법입니다.

#include <stdio.h>

int main() {
  int a;
  float b;

  b = 1.5;
  a = b;

  printf("%d", a);
  return 0;
}

[Result]
1

int형 변수 a에 float형 변수 b를 대입하게 되면,
소수 부분이 생략되고 정수부분만 대입됩니다.

실행은 잘 되지만.........
다음과 같은 warning이 뜹니다.

warning C4244: '=': 'float'에서 'int'(으)로 변환하면서 데이터가 손실될 수 있습니다.

즉, 대입하는 과정에서 데이터 손실이 발생할 수 있다는 건데요. 왜 이런 걸까요??

이건 각 변수들이 메모리 상에 저장되는 특징이 다르기 때문입니다.

그렇다면 컴퓨터는 어떻게 변수들을 저장할까요??


1. 부동 소수점

컴퓨터에서 실수를 보관하는 타입으로는 floatdouble이 있습니다.

컴퓨터에서 실수를 표현하는 방법으로는 대표적으로 고정 소수점(Fixed Point) 방식, 부동 소수점(Floating Point) 방식이 있습니다.

float 타입의 float는 바로 이 부동 소수점(Floating Point)에서 온 거겠죠?

대부분의 컴퓨터는 부동 소수점 방식을 이용해 실수를 표현하고 있습니다.
이는, 고정 소수점에 비해 표현할 수 있는 수의 범위가 넓기 때문입니다.
(하지만, 고정 소수점에 비해 정밀도는 떨어집니다.)

이처럼 부동 소수점 방식으로 수를 표현하는 방법은 국제전기전자기술자협회(IEEE)에서 1985년 'IEEE-754'라는 이름으로 표준화했습니다.

1) 10진법 소수를 2진법 소수로 변환

예를 들어 다음 이진법 수를 십진법 소수로 변환해 봅시다.

10010.1011(2)10010.1011_{(2)}

소수점 이하 부분은 자리수 마다 21,22,...2^{-1}, 2^{-2},...순으로 내려갑니다. 십진법과 동일하죠

10010.1011(2)=24+21+21+23+24=16+2+0.5+0.125+0.0625=18.687510010.1011_{(2)}\\ = 2^4 + 2^1 + 2^{-1} + 2^{-3} + 2^{-4}\\ = 16 + 2 + 0.5 + 0.125 + 0.0625\\ = 18.6875

입니다.

반대로 십진법 소수도 이진법 소수로 변환할 수 있습니다.
하지만!!!!!!! 모든 십진법 소수가 변환 가능한 것은 아닙니다.

0.1=24+25+29+...=0.0001100110011...(2)0.1 \\=2^{-4} + 2^{-5} + 2^{-9} + ... \\=0.0001100110011..._{(2)}

위와 같이 무한소수로 나타나게 됩니다.

따라서 컴퓨터는 무한히 메모리 상에 나타낼 수 없기 때문에 일정 부분만 잘라서 메모리에 보관하게 됩니다.
이 과정에서 필연적으로 오차가 발생하는 것이죠.


2) IEEE-754

보통 우리가 수를 표현하는 방법은 다음과 같습니다.

1231234.123234123\\ 1234.123\\ -234

위의 예는 다음과 같이 동일하게 표현할 수 있습니다.
아래의 방식을 과학적 표기(Scientific notation)이라고 합니다.

1.23×10²1.234123×1022.34×10²1.23 × 10²\\ 1.234123 × 10^{-2}\\ -2.34 × 10²

마찬가지로 컴퓨터 상에서도 소수를 다음과 같이 표현합니다.

±f×be± f × b^e

f는 가수, b는 밑, e는 지수입니다.

예를 들어 f는 1.23, b는 10, e는 2가 됩니다.

컴퓨터에서는 이진체계를 이용하기 때문에 b의 값은 항상 2로 고정되어 있습니다.

따라서 소수 데이터를 보관할 때는 f, e의 값만 저장하면 됩니다.
그리고 맨 앞에는 부호 비트를 위해 1비트를 더 쓰게 됩니다.
(2의 보수 표현법과는 다릅니다.)
부호 비트가 0이면 양수, 1이면 음수가 됩니다.

float형인 경우 부호 비트로 1비트, 지수 부분이 8비트, 가수 부분이 23비트로 총 32비트로, 4바이트를 차지하게 됩니다.

double형인 경우 부호 비트로 1비트, 지수 부분이 11비트, 가수 부분이 52비트로 총 64비트, 8바이트를 차지하게 됩니다.
(왜 double인지 알겠죠?)


3) IEEE-754 방식으로 소수를 저장하기

예를 들어 십진수 118.625(10)=1110110.101(2)-118.625_{(10)} = 1110110.101_{(2)}로 봅시다.

(1) 부호 비트

만약 부호비트에 저장할 소수가 양수라면 0, 음수라면 1이 저장됩니다.

따라서 위의 수의 부호 비트에는 1이 들어가게 됩니다.

(2) 정규화(Normalization)

두번째로는 변환된 이진수를 정규화(Normalization)합니다.

  • 정규화(Normalization)
    : 어떤 이진수를 1.xxxx꼴로 만드는 것입니다.

위의 예시에서는 1110110.1011110110.1011.110110101.11011010로 바꾸는 것입니다.

그러면 가수 부분에는 xxxx부분 즉, 110110101110110101만 저장됩니다.

단, 정규화 작업 시 얼마만큼 쉬프트 연산이 일어났는지 계산해서 지수 부분에는 얼마가 와야 되는지 알아야 합니다.

위의 예시에서는 쉬프트 연산이 오른쪽으로 6번 일어났습니다. 따라서 지수에는 6이 오게 됩니다.

무한소수는 어떨까요?
float의 경우 가수 부분이 23비트이므로, 24번째 비트에서 반올림을 하게 됩니다.

(3) 바이어스(Bias) 처리

지수에 2(e1)12^{(e-1)}-1만큼 더해줍니다.
이 때, e는 지수 부분의 비트 수로 float형은 8, double형은 11입니다.

따라서 float형은 2(81)1=1272^{(8-1)}-1 = 127, double형은 2(111)1=10232^{(11-1)}-1 = 1023을 각각 더해줍니다.

이러한 과정은 왜 필요할까요?

지수는 언제나 양수가 아니기 때문에, 바이어스 처리를 통해 지수를 양수로 만듭니다.


(이 글은 제가 공부한 내용을 정리한 내용으로,
컴퓨터 상에서 적다보니 모든 내용을 정리하지 못했습니다.)

(자세한 내용은 제가 이 글을 작성할 때 인용, 참조한 원본글을 봐주세요. - 모두의 코드(자세한 참조는 이 글 맨 밑에 있습니다.))


2. 형 변환 (Type Casting)

맨 위의 처음 예시에서,
우리는 원하는 결과를 얻지 못했습니다.

그렇다면 아래의 예시는 어떨까요?
double형 변수 b를 int형으로 캐스팅해보겠습니다.

#include <stdio.h>

int main() {
	int a;
	float b;

	b = 1.5;
	a = (int)b;	// Type Casting

	printf("%d", a);
	return 0;
}

[Result]
1

엥???? 결과가 똑같네요.....
하지만, warning 메세지는 출력되지 않습니다.

바로 강제로 형 변환(Type Casting)을 했기 때문입니다.

  • 형 변환(Type Casting)
(바꾸려는 형) 변수 이름

위의 예시에서는 (int)b가 되겠죠.
double형 변수 b를 int형으로 변환했습니다.

이는, 해당 연산에서 일시적으로 변환할 뿐, 영구적인 변환은 아닙니다.

또한, 아래의 예시를 봅시다.

#include <stdio.h>

int main() {
	int int1 = 4, int2 = 3;
	float float1, float2;

	float1 = int1 / int2;
	float2 = (float)int1 / int2;	// Type Casting

	printf("float1 : %f\nfloat2 : %f", float1, float2);
	return 0;
}

[Result]
float1 : 1.000000
float2 : 1.333333

a / b는 2가지 의미를 가집니다.

  1. ab중 하나라도 실수형 변수(float, double)이라면 나눗셈을 합니다.
    ex) 4 / 3 = 1.333333

  2. 만약 둘 다 모두 정수형 변수(char, int, long)이라면 우리가 아는 나눗셈이 아닌 을 계산합니다.
    ex) 4 / 3 = 1

따라서 위의 예시의 float2 = (float)int1 / int2;에서,
컴퓨터는 int1을 실수형 변수로 생각해, 실수형 나눗셈을 수행합니다.


3. Q&A

-


4. 마치며

오늘은 꽤나 이론적인 내용이었습니다.

대학교에서 정말 지독하게 배웠던 내용이네요.
그 때는 왜 그렇게나 이해가 안됐는지 모르겠네요...😂

오늘도 공부하면서 다시 떠올려보니까 꽤나 익숙해서 다행이었습니다.
그 때의 고생이 헛수고가 아니었네요 🤔

[Reference] : 위 글은 다음 내용을 참고, 인용하여 만들어졌습니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글