04.1 정수끼리 나누면 소수점이 사라집니다

계산기로 5 나누기 2를 하면 2.5가 나오는데, C로 같은 계산을 하면 2가 나옵니다. 처음 이걸 본 사람은 프로그램이 고장 난 줄 압니다. 고장이 아니라 C의 규칙입니다. 아래 코드를 그대로 따라 쳐서 실행해 보세요.


#include <stdio.h>


int main(void) {

int a = 5, b = 2;

printf("%d\n", a / b);

return 0;

}


화면에는 이렇게 찍힙니다.


2


이유는 이렇습니다. C에서 모든 값에는 자료형(data type, 값의 종류)이 있고, int는 정수만 담는 자료형입니다. 정수 5와 정수 2를 나눗셈 기호 /로 나누면, C는 결과도 정수여야 한다고 보고 소수점 아래를 통째로 버립니다. 그래서 2.5에서 0.5가 사라지고 몫인 2만 남습니다. 함정. 이건 반올림이 아니라 무조건 버림입니다. 2.9가 나올 계산도 결과는 2가 됩니다.

04.2 형변환은 값의 종류를 바꾸는 일

이 문제를 풀려면 형변환(type conversion, 어떤 값을 다른 자료형으로 바꾸는 것)을 알아야 합니다. 나눗셈에서 소수점이 살아남으려면 둘 중 적어도 하나가 실수(소수를 담는 값)여야 합니다. C는 정수와 실수를 함께 계산할 때 정수를 실수 쪽으로 맞춰 올린 다음 계산하고, 결과도 실수로 냅니다. 이렇게 C가 알아서 맞춰 주는 것을 자동 형변환이라고 합니다.

우리가 직접 이 값을 실수로 취급하라고 지시할 수도 있습니다. 값 앞에 괄호로 자료형 이름을 붙이는 (double)값 문법이며, 이것을 캐스트(cast, 사람이 직접 지시하는 형변환)라고 부릅니다. double은 소수를 담는 실수 자료형입니다.

04.3 (double)로 소수 결과를 되살리기

앞의 나눗셈에 캐스트를 넣어 보겠습니다.


#include <stdio.h>


int main(void) {

int a = 5, b = 2;

printf("정수 나눗셈: %d\n", a / b);

printf("실수로 변환: %.1f\n", (double)a / b);

return 0;

}


출력은 이렇습니다.


정수 나눗셈: 2

실수로 변환: 2.5


둘째 줄에서 (double)aa만 실수로 바꿨는데도 2.5가 나옵니다. 한쪽이 실수가 되면 나머지 b도 자동으로 실수로 따라 올라가기 때문입니다. 출력에 쓴 %.1f는 실수를 소수점 첫째 자리까지 찍으라는 뜻입니다. 주의. 괄호 위치를 헷갈리면 안 됩니다. (double)(a / b)라고 쓰면 a / b가 먼저 정수로 계산되어 2가 된 다음에야 실수로 바뀌므로 2.0이 나옵니다. 형변환은 나누기 전에 걸어야 합니다.

실전에서 가장 자주 만나는 경우가 평균 구하기입니다. 세 사람 점수의 합 250을 인원 3으로 나눠 봅니다.


int sum = 250, n = 3;

printf("틀린 평균: %d\n", sum / n);

printf("맞는 평균: %.2f\n", (double)sum / n);


틀린 평균: 83

맞는 평균: 83.33


합계와 개수는 정수라도 평균은 실수여야 정확합니다. 이럴 때 (double)를 잊으면 소수점이 잘려 미묘하게 틀린 값이 나옵니다.

04.4 (int)로 실수를 정수로 자를 때의 버림

반대 방향도 있습니다. 실수를 정수로 바꾸고 싶을 때입니다. 평점 3.9를 소수점 버리고 정수로 저장해 보겠습니다.


double score = 3.9;

int cut = (int)score;

printf("정수로 자르면: %d\n", cut);


정수로 자르면: 3


(int)는 실수의 소수점 이하를 잘라 버리고 정수 부분만 남깁니다. 함정. 여기서도 반올림이 아니라 버림입니다. 3.9는 4가 아니라 3이 되고, 3.1도 3입니다. 0 방향으로 자르기 때문에 -3.9는 -4가 아니라 -3이 됩니다. 굳이 반올림을 하고 싶다면 자르기 전에 0.5를 더하는 방법이 있습니다.


double x = 3.7;

int rounded = (int)(x + 0.5);

printf("%d\n", rounded);


4


3.7에 0.5를 더해 4.2를 만든 뒤 소수점을 버려 4를 얻습니다. 형변환의 방향과 버림 성질을 알면 이런 응용이 가능합니다.

04.5 문자도 사실은 숫자입니다

형변환을 이해하면 C의 재미있는 성질 하나가 보입니다. 문자 하나를 담는 자료형 char는 겉으로는 글자를 담는 것 같지만, 컴퓨터 안에서는 정수로 저장됩니다. 각 글자마다 정해진 번호가 있는데 이것을 아스키 코드(ASCII code)라고 합니다. 문자 'A'의 번호는 65입니다.


char c = 'A';

printf("문자 %c의 코드값: %d\n", c, c);

printf("코드값 + 1 문자: %c\n", c + 1);


문자 A의 코드값: 65

코드값 + 1 문자: B


[C 04] 형변환과 상수


같은 값 c를 두고 %c는 문자 A로, %d는 숫자 65로 다르게 출력했습니다. 이것도 일종의 형변환입니다. 문자와 정수가 서로 자유롭게 오가기 때문에 c + 1처럼 문자에 숫자를 더하는 계산이 가능하고, 65 다음 번호인 66은 문자 'B'라서 그렇게 찍힙니다. 주의. 작은따옴표 'A'는 문자 하나이고, 큰따옴표 "A"는 문자열(글자들의 묶음)이라 완전히 다릅니다. 문자 자리에 큰따옴표를 쓰지 않도록 조심합니다.

04.6 이름 붙인 상수 #define

이제 상수(constant, 프로그램이 도는 동안 바뀌지 않는 값)로 넘어갑니다. 원의 넓이를 여러 번 구하는데 그때마다 3.14159를 손으로 적는다고 해 봅니다. 오타가 나기 쉽고, 나중에 자릿수를 바꾸려면 모든 자리를 찾아 고쳐야 합니다. 이럴 때 값에 이름을 붙여 둡니다.


#include <stdio.h>

#define PI 3.14159


int main(void) {

double r = 2.0;

printf("원 넓이: %.2f\n", PI * r * r);

return 0;

}


원 넓이: 12.57


#define은 전처리기(preprocessor, 컴파일 전에 소스 코드를 손보는 단계)에게 내리는 지시입니다. 컴파일이 시작되기 전에 코드 속의 PI라는 글자를 전부 3.14159로 그대로 바꿔치기합니다. 그래서 값이 한 군데에만 적혀 있어 고치기 편하고, PI라는 이름 덕분에 읽기도 쉽습니다. 함정. #define 줄 끝에는 세미콜론이나 등호를 붙이지 않습니다. #define PI = 3.14라고 쓰면 PI가 글자 그대로 = 3.14로 치환되어 = 3.14 * r * r이라는 이상한 코드가 되고 맙니다. 단순한 글자 바꿔치기라는 점을 늘 기억해야 합니다.

04.7 바뀌면 안 되는 값을 지키는 const

상수를 만드는 또 다른 방법은 변수 앞에 const를 붙이는 것입니다. 최대 인원처럼 프로그램 중간에 실수로라도 바뀌면 안 되는 값에 씁니다.


const int MAX_USERS = 100;

printf("최대 인원: %d\n", MAX_USERS);

// MAX_USERS = 200; // 이 줄을 켜면 컴파일 에러


최대 인원: 100


const가 붙은 값은 처음 정한 뒤로 바꿀 수 없습니다. 주석을 지우고 MAX_USERS = 200;을 넣으면 컴파일러가 오류를 내며 아예 프로그램을 만들지 못하게 막습니다. 실수를 실행 전에 잡아 주는 안전장치인 셈입니다. #define과 비슷해 보이지만 차이가 있습니다. #define은 자료형이 없는 단순 글자 치환이고, constintdouble 같은 자료형을 가진 진짜 변수라서 컴파일러가 값의 종류까지 검사해 줍니다. 그래서 요즘은 값에 이름을 붙일 때 const를 더 권합니다. 주의. const는 만들 때 값을 함께 정해야 합니다. const int MAX_USERS;처럼 값 없이 선언한 뒤 나중에 넣으려 하면, 그때는 이미 바꿀 수 없는 상태라 에러가 납니다.