본문 바로가기

기본 데이터 타입

정수와 부동 소수점, 문자, 논리 값을 담는 C++의 기본 타입이 표준이 보장하는 것과 구현이 정하는 것을 나누어 보고, sizeof와 std::numeric_limits로 지금 컴파일러의 크기와 범위를 확인한 뒤, 값의 의미와 범위로 타입을 고르는 기준을 세웁니다.

int score = 100;에서 int는 이 변수가 정수를 담는다는 것뿐 아니라, 담을 수 있는 값의 범위와 계산하는 규칙, 메모리에서 차지하는 크기까지 정합니다. 같은 100이라도 사람의 수인지, 온도인지, 문자의 코드인지에 따라 알맞은 타입이 다릅니다.

C++의 기본 타입은 크기를 하나로 정해 두지 않고 최소한의 조건만 정한 뒤 나머지는 컴파일러와 플랫폼에 맡깁니다. 그래서 이 글은 표준이 보장하는 것과 구현이 정하는 것을 나누어 보고, 지금 쓰는 컴파일러의 값을 sizeof와 std::numeric_limits로 확인하는 방법을 익힙니다.

C++ · TYPE AND SCOPE

타입은 값의 의미와 표현 범위부터 선택한다

흔한 바이트 수와 표준 보장을 구분하고, 현재 구현의 범위·정밀도·인코딩을 확인합니다.

타입 이름이나 흔한 바이트 수를 외우기보다 값의 의미를 먼저 정하고, 표준 계약과 현재 구현의 값을 분리해 확인한다.
타입군 먼저 묻는 질문 표준 계약 · 주의점 현재 구현 확인
부호 있는 정수 입력과 중간 계산 값의 범위는? short → int → long → long long은 저장 공간이 줄지 않는다. 표현 범위를 넘는 signed 산술은 정의되지 않는다. sizeof(T), std::numeric_limits<T>
부호 없는 정수 순환 산술이나 비트 패턴이 실제 의미인가? 대응 signed 타입과 같은 폭 N, 범위 0 … 2^N−1, 산술은 2^N을 법으로 한다. std::numeric_limits<T>::max(), signed/unsigned 혼합 식 점검
부동 소수점 필요한 정밀도와 허용 오차 모델은? double은 float 이상, long double은 double 이상의 정밀도를 제공하지만 같은 표현일 수도 있다. radix, digits10, denorm_min(), lowest()/max(), is_iec559
문자 · 코드 단위 어떤 리터럴·문자열 인코딩을 다루는가? char 부호와 일반·wide 인코딩은 구현 정의다. char8_t/16_t/32_t는 각 UTF 인코딩의 코드 단위다. CHAR_BIT, sizeof, 리터럴 prefix와 입출력 인코딩
논리 값이 계산량이 아니라 조건의 상태인가? bool의 값은 true와 false다. 크기는 구현 정의이며 정수 개수 저장용 타입이 아니다. 두 상태를 넘는 이름 있는 상태 집합이면 enum을 검토한다. sizeof(bool), boolalpha 출력 여부
부호 있는 정수
질문 입력과 중간 계산 값의 범위는?
계약 short → int → long → long long 순서에서 저장 공간은 줄지 않으며, 범위를 넘는 signed 산술은 정의되지 않습니다.
확인 sizeof와 std::numeric_limits<T>
부호 없는 정수
질문 순환 산술이나 비트 패턴이 실제 의미인가?
계약 대응 signed 타입과 같은 폭 N이며 범위는 0 … 2^N−1이고 산술은 2^N을 법으로 합니다.
확인 최댓값과 signed/unsigned 혼합 식
부동 소수점
질문 필요한 정밀도와 허용 오차 모델은?
계약 double은 float 이상, long double은 double 이상의 정밀도를 제공하지만 같은 표현일 수도 있습니다.
확인 radix, digits10, denorm_min(), lowest()/max(), is_iec559
문자 · 코드 단위
질문 어떤 리터럴·문자열 인코딩을 다루는가?
계약 일반 char의 부호와 ordinary·wide 인코딩은 구현이 정합니다. char8_t/16_t/32_t는 각각 UTF-8/16/32 코드 단위입니다.
확인 CHAR_BIT, 크기, prefix, 입출력 인코딩
논리
질문 계산량이 아니라 조건의 상태인가?
계약 bool은 true와 false를 표현하며 크기는 구현 정의입니다. 두 상태를 넘는 이름 있는 상태 집합이면 enum을 검토합니다.
확인 sizeof(bool)과 boolalpha 출력

고정폭이나 wire format이 요구되면 타입 이름만 선택하지 말고 <cstdint>의 제공 여부, 바이트 순서, 직렬화 규칙을 별도 계약으로 둡니다.

그림은 타입을 고를 때 묻는 질문을 타입의 종류별로 정리합니다. 정수는 입력과 중간 계산의 범위를, 부호 없는 정수는 순환하는 계산이 정말 그 값의 뜻인지를, 부동 소수점은 필요한 정밀도와 오차를, 문자는 다루는 인코딩을 먼저 묻고, 각 타입의 실제 값은 지금 구현에서 확인합니다.

타입이 정하는 것

데이터 타입(data type)은 값을 어떻게 저장하고 해석하며 계산할지를 정하는 속성입니다. 변수를 선언할 때 타입을 정하면, 컴파일러는 그 변수가 어떤 값을 담을 수 있는지, 메모리를 얼마나 쓰는지, 어떤 연산을 어떤 규칙으로 할지를 알게 됩니다.

C++ · TYPE AND SCOPE

하나의 타입이 표현·연산·객체 계약을 함께 정한다

가능한 값, 연산 규칙, 크기와 정렬을 함께 확인해야 실행 결과를 설명할 수 있습니다.

C++ 타입이 만드는 세 가지 계약 값의 의미에 맞춘 선언과 식의 타입에서 표현 범위와 정밀도, 연산과 변환 규칙, 객체 크기와 정렬이라는 세 계약이 파생되고, 이 계약들이 함께 컴파일 결과와 실행 의미를 결정한다. 선언과 식의 타입 값의 의미에 맞춰 선택 표현 계약 가능한 값 · 범위 정밀도 · 코드 단위 연산 계약 산술 · 비교 · 변환 오버플로 · 반올림 객체 계약 크기 · 정렬 구현별 표현 특성 컴파일 결과 · 실행 의미 세 계약이 함께 관찰 가능한 동작을 만든다
타입의 세 계약은 함께 작용한다선언과 식의 타입에서 표현·연산·객체 계약으로 각각 갈라지고 세 계약이 컴파일과 실행 의미로 함께 이어진다. 세로 배치는 표현 다음 연산 다음 객체라는 실행 순서가 아니다. 표현 계약의 출력선과 객체 계약의 입력선은 홉으로 교차하며 합류하지 않는다.선언과 식의 타입값의 의미로 선택표현 계약가능한 값 · 범위정밀도 · 코드 단위연산 계약산술 · 비교 · 변환오버플로 · 반올림객체 계약크기 · 정렬구현별 표현 특성컴파일 · 실행 의미세 계약을 함께 확인
type contract 선언과 식의 타입

값의 의미에 맞춰 타입을 정하면 아래 세 계약이 함께 결정됩니다.

표현 계약
가능한 값과 범위, 정밀도, 문자 코드 단위를 정합니다.
연산 계약
산술·비교·변환, 오버플로와 반올림 규칙을 정합니다.
객체 계약
크기·정렬과 구현별 표현 특성을 정합니다.

컴파일 결과 · 실행 의미

세 계약이 함께 관찰 가능한 동작을 만듭니다.

검증할 때는 sizeof, std::numeric_limits, 경계값 테스트로 현재 구현과 의도를 대조합니다.

같은 숫자처럼 보여도 타입이 다르면 표현 가능 범위와 연산·변환 결과가 달라질 수 있습니다. 특정 바이트 배열을 모든 환경의 공통 표현으로 가정하지 않습니다.

그림처럼 하나의 타입은 세 가지를 함께 정합니다. 담을 수 있는 값의 범위와 정밀도, 산술과 비교와 변환의 규칙, 객체의 크기와 정렬입니다. 예를 들어 정수 10과 실수 10.0은 같은 수처럼 보이지만, 메모리에 저장되는 비트의 모양과 나눗셈의 결과가 다릅니다. 이 세 가지는 차례로 정해지는 것이 아니라 함께 작용해 프로그램의 결과를 만듭니다.

정수 타입

정수 타입은 소수점이 없는 수를 담습니다. C++은 크기가 다른 여러 정수 타입을 제공하며, 표준은 각 타입의 최소 폭만 정합니다.

타입표준의 최소 폭흔한 sizeof쓰임과 주의할 점
char8비트1문자의 코드 단위이자 C++의 바이트이며, 부호의 해석은 구현이 정합니다.
short16비트2작은 정수입니다.
int16비트4일반적인 정수 계산의 기본입니다.
long32비트4 또는 8Windows는 4, Linux와 macOS의 64비트 환경은 8로, 플랫폼마다 다르기 쉽습니다.
long long64비트8표준 정수 타입 가운데 최소 폭이 가장 큽니다.

표준은 signed char, short, int, long, long long의 순서로 뒤의 타입이 앞의 타입보다 작지 않아야 한다고만 정하므로, 표의 흔한 크기는 많은 구현이 쓰는 값일 뿐입니다. 폭이 N비트인 부호 있는 정수는 −2N−1-2^{N-1}부터 2N−1−12^{N-1}-1까지를 담으며, 예를 들어 32비트 int는 -2147483648부터 2147483647까지입니다.

sizeof(char)는 언제나 1이지만, 이 1은 C++이 정한 바이트 하나를 뜻합니다. 한 바이트가 몇 비트인지는 <climits>의 CHAR_BIT로 확인하며, 오늘날 거의 모든 환경에서 8이지만 표준은 8 이상만 요구합니다. 또 char는 signed char, unsigned char와 서로 다른 세 번째 타입이고, char가 부호를 가지는지는 구현이 정합니다.

short, int, long, long long은 앞에 signed를 생략한 부호 있는 타입이며, 각각 같은 폭의 부호 없는 타입이 짝으로 있습니다. 폭이 N비트인 부호 없는 정수는 0부터 2N−12^N-1까지를 담고, 계산은 2N2^N으로 나눈 나머지로 합니다. 그래서 최댓값에 1을 더하면 0이 되고, 0에서 1을 빼면 최댓값이 됩니다.

이렇게 값이 반대쪽으로 순환하는 것이 비트 마스크나 통신 형식처럼 정말 그 값의 뜻이라면 unsigned가 알맞습니다. 하지만 음수가 없을 것이라는 이유만으로 고르면, 두 수의 차를 구하거나 부호 있는 값과 비교하는 곳에서 버그를 숨기기 쉽습니다. 반면 부호 있는 정수의 계산 결과가 범위를 넘는 것은 순환이 아니라 정의되지 않은 동작(undefined behavior)이어서, 결과를 전혀 믿을 수 없습니다.

integer_types.cpp
#include <climits>
#include <iostream>
#include <limits>

int main() {
    char singleChar = 'A';
    short smallNumber = 100;
    int integerValue = 12345;
    long bigNumber = 1234567890L;
    long long veryBigNumber = 9000000000000000000LL;
    unsigned int positiveOnly = 42U;

    std::cout << "char: " << singleChar << ", sizeof " << sizeof(char) << "\n";
    std::cout << "short: " << smallNumber << ", sizeof " << sizeof(short) << "\n";
    std::cout << "int: " << integerValue << ", sizeof " << sizeof(int) << "\n";
    std::cout << "long: " << bigNumber << ", sizeof " << sizeof(long) << "\n";
    std::cout << "long long: " << veryBigNumber << ", sizeof " << sizeof(long long) << "\n";
    std::cout << "unsigned int: " << positiveOnly << ", sizeof " << sizeof(unsigned int) << "\n";
    std::cout << "CHAR_BIT: " << CHAR_BIT << "\n";
    std::cout << "int max: " << std::numeric_limits<int>::max() << "\n";
    std::cout << "int min: " << std::numeric_limits<int>::min() << "\n";
    std::cout << "unsigned int max: " << std::numeric_limits<unsigned int>::max() << "\n";
}

개발 환경 설정의 Windows x86-64용 MSYS2 GCC에서는 다음과 같이 출력됩니다. 표준은 타입마다 최소한의 크기만 정하고 정확한 크기는 컴파일러와 플랫폼이 정하므로, sizeof와 최댓값, 최솟값이 나오는 줄은 이 환경에서의 결과로 읽어야 합니다.

char: A, sizeof 1
short: 100, sizeof 2
int: 12345, sizeof 4
long: 1234567890, sizeof 4
long long: 9000000000000000000, sizeof 8
unsigned int: 42, sizeof 4
CHAR_BIT: 8
int max: 2147483647
int min: -2147483648
unsigned int max: 4294967295

sizeof는 타입이나 변수가 차지하는 바이트 수를 알려 주는 연산자이고, <limits>의 std::numeric_limits<T>는 타입 T의 최댓값과 최솟값 같은 성질을 알려 줍니다. 리터럴 뒤의 L, LL, U는 그 수를 long, long long, unsigned 타입으로 적는 접미사입니다. Linux와 macOS의 64비트 환경에서 같은 프로그램을 실행하면 long의 sizeof가 8로 나오며, 이것은 오류가 아니라 플랫폼이 정하는 차이입니다.

더 깊이: C++20이 정한 2의 보수 표현

C++17까지의 표준은 부호 있는 정수를 2의 보수(two's complement)뿐 아니라 1의 보수나 부호와 크기 표현으로 나타내는 것도 허용했습니다. 그래서 8비트 부호 있는 타입이 보장하는 범위도 -128이 아니라 -127부터 127까지였습니다. 실제로는 GCC와 LLVM, MSVC가 2의 보수만 지원했고, 다른 표현을 쓰는 현대의 기계에서 C++을 쓰는 예도 알려져 있지 않았으므로, C++20은 P0907을 받아들여 2의 보수만 허용하도록 바꾸었습니다. 본문의 −2N−1-2^{N-1}부터 2N−1−12^{N-1}-1까지라는 범위도 이때부터 표준이 보장하는 범위입니다. 세 표현이 같은 비트를 어떻게 다르게 읽는지는 음수·보수·넘침에서 비교합니다.

표현이 정해지면서 함께 정해진 동작도 있습니다. 범위를 벗어난 정수를 부호 있는 타입으로 바꾸는 변환은 C++17까지 구현이 정하는 결과였지만, C++20부터는 원래 값을 2N2^N으로 나눈 나머지와 같은 값이 됩니다. 예를 들어 4294967295u를 32비트 int로 바꾸면 -1입니다. 음수를 오른쪽으로 시프트하면 빈 자리를 부호 비트로 채우는 산술 시프트가 된다는 것도 이때 정해졌습니다.

하지만 부호 있는 정수의 산술 오버플로는 C++20에서도 정의되지 않은 동작으로 남았습니다. 이 논문의 최종 개정판은 순환하는 결과로 정하는 대신 정의되지 않은 동작을 유지했는데, 성능과 진단 도구의 유연성, 그리고 오버플로의 대부분이 버그라는 점을 이유로 들었습니다. 그래서 비트 표현을 안다고 해서 INT_MAX + 1이 음수가 되리라고 기대하면 안 됩니다.

부동 소수점 타입

부동 소수점 타입은 소수점이 있는 실수를 담습니다. 표준은 세 타입의 관계만 정하며, 실제 표현은 구현이 정합니다.

타입표준이 보장하는 것흔한 구현
float가장 작은 부동 소수점 타입4바이트, 십진수 약 6자리
doublefloat 이상의 정밀도8바이트, 십진수 약 15자리
long doubledouble 이상의 정밀도구현마다 8, 12, 16바이트 등으로 다름

흔한 구현의 float과 double은 IEEE 754의 32비트와 64비트 이진 형식이며, 이것은 std::numeric_limits<T>::is_iec559가 true인지로 확인합니다. 이 형식이 부호와 지수, 분수 필드로 수를 나타내고 반올림하는 방법은 소수·부동소수점·문자에서 다룹니다. 정밀도가 약 6자리와 15자리라는 것은 digits10이 6과 15라는 뜻으로, 그 자릿수의 십진수를 타입에 넣었다가 되돌렸을 때 같은 수가 나온다는 보장입니다. 그보다 짧은 모든 소수가 정확히 저장된다는 뜻은 아닙니다. long double은 double과 같은 표현일 수도 있으므로, 이름만 보고 더 정확하다고 가정하지 않습니다.

std::numeric_limits의 값 가운데 헷갈리기 쉬운 것이 있습니다. 부동 소수점의 min()은 가장 작은 음수가 아니라 가장 작은 양의 정규화 값이고, 그보다 더 작은 양수인 서브노멀(subnormal) 값을 지원하면 denorm_min()이 그 최솟값을 돌려줍니다. 가장 작은 유한한 값, 즉 가장 큰 음수는 lowest()이고 가장 큰 유한한 값은 max()입니다.

이진 부동 소수점은 0.1 같은 많은 십진 소수를 정확히 담지 못하고, 가장 가까운 이진수로 반올림해 저장합니다. 그래서 계산할 때마다 작은 오차가 쌓일 수 있으며, 두 실수가 같은지 비교할 때는 == 대신 값의 크기와 단위에 맞는 허용 오차를 정해 그 안에 드는지를 봅니다.

floating_types.cpp
#include <iomanip>
#include <iostream>

int main() {
    float pi_float = 3.1415926535F;
    double pi_double = 3.14159265358979323846;

    std::cout << std::fixed << std::setprecision(20);
    std::cout << "float: " << pi_float << ", sizeof " << sizeof(float) << "\n";
    std::cout << "double: " << pi_double << ", sizeof " << sizeof(double) << "\n";
    std::cout << "0.1 + 0.2 = " << 0.1 + 0.2 << "\n";
    std::cout << "sizeof(long double): " << sizeof(long double) << "\n";
}

float과 double이 IEEE 754의 이진 형식인 구현에서는 앞의 세 줄이 다음과 같습니다. 마지막 줄의 long double 크기는 구현마다 달라, 예를 들어 x86-64의 GCC는 16을, MSVC는 8을 출력합니다.

float: 3.14159274101257324219, sizeof 4
double: 3.14159265358979311600, sizeof 8
0.1 + 0.2 = 0.30000000000000004441

F 접미사는 리터럴을 float로 적는 표시이며, 접미사가 없는 실수 리터럴은 double입니다. std::fixed와 std::setprecision(20)은 <iomanip>의 조작자로 소수점 아래 20자리까지 보여 주지만, 저장된 정밀도를 늘리지는 않습니다. float는 처음 저장할 때 가장 가까운 값 3.1415927410125732421875로 반올림되어 일곱째 자리부터 원래의 π와 달라지고, 그 뒤의 숫자는 이미 저장된 값을 보여 줄 뿐입니다. 0.1과 0.2도 각각 반올림되어 저장되므로 그 합은 정확히 0.3이 아닙니다.

더 깊이: 16바이트 long double에 담긴 것

x86-64의 GCC에서 sizeof(long double)가 16이어도, 16바이트가 모두 값을 담는 것은 아닙니다. 이 long double은 x87 부동 소수점 장치의 80비트 확장 정밀도 형식입니다. GCC 문서에 따르면 x86-64용 GCC는 ABI가 long double을 16바이트 단위로 정렬하도록 정했기 때문에 -m128bit-long-double을 기본으로 쓰며, 이런 크기 옵션은 x87의 80비트보다 정밀도를 더해 주지 않습니다. 즉 10바이트가 값이고 나머지는 정렬을 맞추는 자리입니다.

MSVC는 long double을 double과 같은 64비트 형식으로 구현하므로, 같은 소스라도 컴파일러에 따라 long double의 정밀도와 범위가 달라집니다. 80비트 형식은 지수의 범위도 넓어서 최댓값이 약 1.19×1049321.19 \times 10^{4932}이지만, double의 최댓값은 약 1.80×103081.80 \times 10^{308}입니다. 그래서 long double 값을 다른 컴파일러로 만든 프로그램과 주고받거나 그대로 파일에 저장한다면, 크기만이 아니라 형식까지 같은지 std::numeric_limits<long double>의 digits와 max()로 먼저 확인합니다.

문자 타입

문자 타입은 글자의 모양이 아니라, 문자열의 인코딩을 이루는 하나의 코드 단위(code unit)를 담습니다. UTF-8에서 한글 한 글자는 세 개의 코드 단위로 저장되듯이, 한 글자가 언제나 코드 단위 하나인 것은 아닙니다.

타입담는 것
char일반 문자와 문자열 리터럴의 코드 단위이며, 인코딩은 구현이 정합니다.
wchar_t와이드 리터럴의 코드 단위이며, 크기와 인코딩이 구현마다 다릅니다.
char8_tC++20부터 있는 UTF-8의 코드 단위로, u8"..." 리터럴의 원소입니다.
char16_t, char32_tUTF-16과 UTF-32의 코드 단위입니다.

wchar_t는 Windows에서는 16비트, Linux에서는 32비트인 것처럼 플랫폼마다 달라, 여러 플랫폼에서 같은 뜻으로 쓰는 유니코드 타입으로는 알맞지 않습니다. UTF-16에서는 이모지처럼 기본 다국어 평면 밖의 글자가 두 코드 단위를 쓰므로, char16_t 하나가 글자 하나라고 가정하지 않습니다.

char_type.cpp
#include <iostream>

int main() {
    char grade = 'A';
    std::cout << "grade: " << grade << "\n";
    std::cout << "code unit: " << static_cast<unsigned int>(static_cast<unsigned char>(grade)) << "\n";
}
grade: A
code unit: 65

char를 std::cout으로 출력하면 글자가 보이고, 정수로 바꾸어 출력하면 코드 단위의 값이 보입니다. GCC는 기본으로 UTF-8로 일반 리터럴을 인코딩하므로 'A'는 65가 됩니다. 먼저 unsigned char로 바꾼 것은, char가 부호를 가지는 구현에서 128 이상의 코드 단위가 음수로 바뀌지 않게 하기 위해서입니다.

논리 타입

논리 타입 bool은 true와 false 두 값만 담으며, 조건문과 반복문에서 조건이 맞는지를 나타냅니다. 크기는 대부분의 구현에서 1이지만, 정확한 값은 sizeof(bool)로 확인합니다.

bool_type.cpp
#include <iostream>

int main() {
    bool isCppFun = true;
    bool hasErrors = false;

    std::cout << "isCppFun: " << isCppFun << "\n";
    std::cout << "hasErrors: " << hasErrors << "\n";
    std::cout << std::boolalpha << "isCppFun: " << isCppFun << "\n";

    if (isCppFun) {
        std::cout << "C++ is fun!\n";
    }
}
isCppFun: 1
hasErrors: 0
isCppFun: true
C++ is fun!

bool을 그냥 출력하면 true는 1로, false는 0으로 보입니다. std::boolalpha를 스트림에 보내면 그 뒤로는 true와 false라는 낱말로 출력되며, 이 설정은 std::noboolalpha로 되돌릴 때까지 그 스트림에 남습니다.

타입을 고르는 기준

기본 타입을 고를 때는 숫자인지 문자인지만 보지 말고, 값의 뜻과 필요한 범위, 음수가 필요한지와 순환하는 계산이 그 값의 뜻인지, 실수라면 필요한 정밀도와 허용할 오차, 문자라면 인코딩과 출력 방식을 차례로 봅니다. 파일이나 통신 형식처럼 정확한 폭이 정해져 있는 곳에서는 <cstdint>의 std::int32_t 같은 고정 폭 타입을 쓰며, 그런 타입을 그 구현이 제공하는지도 확인합니다.

정리

  • 기본 타입은 값의 범위와 계산 규칙, 객체의 크기를 함께 정하며, 표준은 최소 조건만 정하고 실제 크기와 표현은 구현이 정합니다.
  • 정수 타입은 short, int, long, long long의 순서로 작아지지 않으며, long처럼 플랫폼마다 크기가 다른 타입은 sizeof와 std::numeric_limits로 확인합니다.
  • 부호 없는 정수는 2N2^N으로 나눈 나머지로 순환하지만, 부호 있는 정수의 범위를 넘는 계산은 정의되지 않은 동작입니다.
  • 부동 소수점은 많은 십진 소수를 반올림해 저장하므로 오차가 생기며, setprecision은 보여 주는 자릿수를 늘릴 뿐 정밀도를 늘리지 않습니다.
  • 문자 타입은 인코딩의 코드 단위를 담으며, 한 글자가 여러 코드 단위일 수 있고 char의 부호와 wchar_t의 크기는 구현이 정합니다.
  • bool은 true와 false를 담고, 출력하면 1과 0으로 보이지만 std::boolalpha로 낱말로 출력할 수 있습니다.

연습 문제

1. 255까지 담는 타입

어떤 구현에서 CHAR_BIT가 8이고 unsigned char의 최댓값이 255입니다. 최대 300명인 참가자 수를 이 타입에 담아도 되나요? 온도를 -10도부터 40도까지 정수로 기록한다면 부호 없는 타입을 고를 이유가 있나요?

해설 보기

300은 255보다 크므로 unsigned char에 담을 수 없으며, 300을 담을 수 있는 범위인지 numeric_limits로 확인한 더 넓은 정수 타입을 씁니다. 온도는 음수가 있으므로 부호 있는 타입을 고르며, 음수가 들어올 수 없다고 가정하고 부호 없는 타입을 쓰면 -10이 큰 양수로 바뀌어 저장됩니다.

2. 0에서 1을 빼면

unsigned int count = 0;에서 count - 1을 출력하면 32비트 unsigned int를 쓰는 구현에서 무엇이 나오나요? int value = std::numeric_limits<int>::max();에서 value + 1은 어떻게 되나요?

해설 보기

부호 없는 계산은 2322^{32}로 나눈 나머지로 순환하므로 count - 1은 4294967295입니다. value + 1은 부호 있는 정수의 범위를 넘으므로 정의되지 않은 동작이어서, 음수로 바뀐다고도 단정할 수 없고 컴파일러가 그런 일이 없다고 가정해 코드를 최적화할 수도 있습니다.

3. 소수점 아래 20자리

본문의 float 출력이 일곱째 자리부터 π와 달라지는 것은 setprecision(20) 때문인가요? pi_double의 출력은 몇째 자리부터 π와 달라지나요?

해설 보기

setprecision 때문이 아닙니다. float에 저장할 때 이미 가장 가까운 값 3.1415927410125732421875로 반올림되었고, setprecision은 그 저장된 값을 20자리까지 보여 줄 뿐입니다. pi_double의 출력 3.14159265358979311600은 π 3.14159265358979323846과 소수점 아래 열여섯째 자리부터 다르며, 이것도 저장할 때의 반올림 때문입니다.

다음 글 변수 선언과 초기화에서는 이 타입들로 변수를 선언하고, 초기화하는 여러 방법과 초기화하지 않은 변수를 읽을 때 생기는 문제를 다룹니다.