산술 타입

산술 타입 (Arithmetic types)

C 프로그램에서 다루는 값의 종류는 크게 나눠보면 정수와 부동소수점으로 갈라져요. 이 둘을 통틀어 산술 타입(arithmetic types)이라고 불러요. 이 페이지에서 C가 제공하는 산술 타입의 종류와 각 구현이 고르는 데이터 모델에 따른 크기, 그리고 값의 범위를 정리해 볼게요.

출처: cppreference

본문

산술 타입은 다음과 같이 분류해요.

  • 불리언 타입(boolean type)
  • 문자 타입(character types)
  • 정수 타입(integer types)
  • 실수 부동소수점 타입(real floating types)
  • 복소 부동소수점 타입(complex floating types)
  • 허수 부동소수점 타입(imaginary floating types)

(타입 시스템의 개요와 C 라이브러리가 제공하는 타입 관련 유틸리티 목록은 타입 문서도 참고하세요.)

불리언 타입 (Boolean type)

_Bool(C23 이전, 매크로 bool로도 접근 가능)/bool(C23 이후) — 두 값 10 중 하나를 담을 수 있는 타입이에요 (C23 이전에서는 매크로 true, false로, C23 이후에는 키워드로 접근).

주의할 점은 _Bool(C23 이후 bool)로의 변환이 다른 정수 타입으로의 변환과 같지 않다는 거예요. (bool)0.5true로 평가되는 반면, (int)0.50으로 평가돼요. (since C99)

문자 타입 (Character types)

  • signed char — 부호 있는 문자 표현용 타입.
  • unsigned char — 부호 없는 문자 표현용 타입. 객체 표현(원시 메모리)을 검사할 때도 써요.
  • char — 문자 표현용 타입. signed charunsigned char 중 하나와 동등한데(어느 쪽인지는 구현-정의이고 컴파일러 명령줄 스위치로 조절할 수 있어요), charsigned charunsigned char 둘 다와 다른 구별되는 타입이에요.

참고로 표준 라이브러리는 와이드 문자를 나타내는 typedef 이름 wchar_t, char16_t, char32_t(since C11)와 UTF-8 문자용 char8_t(since C23)도 정의해요.

정수 타입 (Integer types)

  • short int (short, 또는 키워드 signed와 함께 쓸 수 있음)
  • unsigned short int (unsigned short)
  • int (signed int) — 이 플랫폼에서 가장 최적인 정수 타입이고, 최소 16비트임이 보장돼요. 대부분의 현재 시스템은 32비트를 써요 (아래 데이터 모델 참고).
  • unsigned int (unsigned) — int의 부호 없는 대응 타입으로 모듈로 산술을 구현해요. 비트 조작에 적합해요.
  • long int (long)
  • unsigned long int (unsigned long)
  • long long int (long long) / unsigned long long int (unsigned long long) (since C99)
  • _BitInt(n) (signed _BitInt(n)으로도 쓸 수 있음) — 비트 정밀 부호 있는 정수 타입. 여기서 n은 부호 비트를 포함한 정확한 폭을 나타내는 정수 상수 표현식으로 바뀌고, limits.hBITINT_MAXWIDTH보다 클 수 없어요. unsigned _BitInt(n) — 비트 정밀 부호 없는 정수 타입. (since C23)

참고: 모든 타입 지정자와 마찬가지로 어떤 순서도 허용돼요. unsigned long long intlong int unsigned long은 같은 타입을 가리켜요.

다음 표는 사용 가능한 모든 정수 타입과 그 속성을 요약해요.

Type specifier Equivalent type Width in bits by data model
C standard LP32 ILP32 LLP64 LP64
char char at least 8 8 8 8 8
signed char signed char
unsigned char unsigned char
short short int at least 16 16 16 16 16
short int
signed short
signed short int
unsigned short unsigned short int
unsigned short int
int int at least 16 16 32 32 32
signed
signed int
unsigned unsigned int
unsigned int
long long int at least 32 32 32 32 64
long int
signed long
signed long int
unsigned long unsigned long int
unsigned long int
long long long long int (C99) at least 64 64 64 64 64
long long int
signed long long
signed long long int
unsigned long long unsigned long long int (C99)
unsigned long long int

최소 비트 수 외에도 C 표준은 다음을 보장해요.

1 == sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)

참고: 이는 극단적인 경우를 허용하는데, 바이트가 64비트라서 모든 타입(참고: char 포함)이 64비트이고 sizeof가 모든 타입에 대해 1을 반환하는 경우예요.

참고: 정수 산술은 부호 있는 타입과 부호 없는 타입에 대해 다르게 정의돼요. 산술 연산자 문서, 특히 정수 오버플로를 참고하세요.

데이터 모델 (Data models)

각 구현이 기본 타입의 크기에 대해 내린 선택을 통틀어 데이터 모델(data model)이라고 불러요. 널리 받아들여진 데이터 모델은 네 가지예요.

32비트 시스템:

  • LP32 또는 2/4/4 (int는 16비트, long과 포인터는 32비트) — Win16 API
  • ILP32 또는 4/4/4 (int, long, 포인터 모두 32비트) — Win32 API, Unix 및 Unix 계열 시스템(Linux, Mac OS X)

64비트 시스템:

  • LLP64 또는 4/4/8 (intlong은 32비트, 포인터는 64비트) — Win64 API
  • LP64 또는 4/8/8 (int는 32비트, long과 포인터는 64비트) — Unix 및 Unix 계열 시스템(Linux, Mac OS X)

다른 모델은 아주 드물어요. 예를 들어 ILP64(8/8/8: int, long, 포인터 모두 64비트)는 초기 64비트 Unix 시스템 몇 개(Cray의 Unicos 등)에만 등장했어요.

C99부터 정확한 폭의 정수 타입은 <stdint.h>에서 쓸 수 있다는 점도 참고하세요.

실수 부동소수점 타입 (Real floating types)

C는 실수 부동소수점 값을 나타내는 타입으로 세 개(또는 C23 이후 여섯 개)를 제공해요.

  • float — 단정밀도 부동소수점 타입. 지원된다면 IEEE-754 binary32 형식과 일치해요.
  • double — 배정밀도 부동소수점 타입. 지원된다면 IEEE-754 binary64 형식과 일치해요.
  • long double — 확장 정밀도 부동소수점 타입. 지원된다면 IEEE-754 binary128과 일치하고, 아니면 지원된다면 binary64-extended와 일치하고, 아니면 정밀도가 binary64보다 낫고 범위가 binary64만큼 좋은 비-IEEE-754 확장 부동소수점 형식과 일치하고, 아니면 IEEE-754 binary64 형식과 일치해요.
    • binary128 형식은 일부 HP-UX, SPARC, MIPS, ARM64, z/OS 구현에서 쓰여요.
    • 가장 잘 알려진 IEEE-754 binary64-extended 형식은 80비트 x87 확장 정밀도 형식이에요. 많은 x86, x86-64 구현이 쓰지만(주목할 예외는 MSVC — long doubledouble과 같은 형식, 즉 binary64로 구현해요).

구현이 매크로 상수 __STDC_IEC_60559_DFP__를 미리 정의하면 다음 십진 부동소수점 타입도 지원돼요. 그렇지 않으면 이 타입들은 지원되지 않아요. (since C23)

  • _Decimal32 — IEEE-754 decimal32 형식을 나타냄
  • _Decimal64 — IEEE-754 decimal64 형식을 나타냄
  • _Decimal128 — IEEE-754 decimal128 형식을 나타냄

부동소수점 타입은 특수 값을 지원할 수 있어요.

  • 무한대(양수와 음수) — INFINITY 참고
  • 음의 영, -0.0 — 양의 영과 비교 시 같지만 일부 산술 연산에서 의미가 있어요. 예를 들어 1.0 / 0.0 == INFINITY이지만 1.0 / -0.0 == -INFINITY예요.
  • 수가 아님(NaN) — 무엇과도(자기 자신과도) 같지 않아요. 여러 비트 패턴이 NaN을 나타내며, nan, NAN 참고. C는 시그널링 NaN(IEEE-754가 명시)을 특별히 처리하지 않고 모든 NaN을 quiet으로 취급해요.

실수 부동소수점 수는 산술 연산자 + - / *<math.h>의 여러 수학 함수와 함께 쓸 수 있어요. 내장 연산자와 라이브러리 함수 모두 math_errhandling에 서술된 대로 부동소수점 예외를 일으키고 errno를 설정할 수 있어요.

부동소수점 표현식은 타입이 나타내는 것보다 더 큰 범위와 정밀도를 가질 수 있어요. FLT_EVAL_METHOD 참고. 대입, 반환, cast는 범위와 정밀도를 선언된 타입과 연관된 것으로 강제해요.

부동소수점 표현식은 또한 수축(contract)될 수 있어요. 즉 모든 중간 값이 무한한 범위와 정밀도를 가진 것처럼 계산될 수 있어요. #pragma STDC FP_CONTRACT 참고.

부동소수점 수에 대한 일부 연산은 부동소수점 환경의 상태(가장 주목할 만한 것은 반올림 방향)에 영향을 받고 수정해요.

실수 부동소수점 타입과 정수, 복소, 허수 타입 사이에는 암시적 변환이 정의돼 있어요.

부동소수점 타입의 한계·속성에 대한 추가 세부사항은 부동소수점 타입의 한계와 <math.h> 라이브러리를 참고하세요.

복소 부동소수점 타입 (Complex floating types)

복소 부동소수점 타입은 수학의 복소수를 구현해요. 실수 부분과 허수 부분을 가진 bi 형태로 쓸 수 있는 수들이죠. 세 가지 복소 타입이 있어요.

  • float _Complex (<complex.h>를 포함하면 float complex로도 사용 가능)
  • double _Complex (double complex)
  • long double _Complex (long double complex)

구현이 매크로 상수 __STDC_NO_COMPLEX__를 정의하면, 복소 타입(및 라이브러리 헤더 complex.h)은 제공되지 않아요. (since C11)

#include <complex.h>
#include <stdio.h>

int main(void)
{
    double complex z = 1 + 2*I;
    z = 1 / z;
    printf("1/(1.0+2.0i) = %.1f%+.1fi\n", creal(z), cimag(z));
}

출력:

1/(1.0+2.0i) = 0.2-0.4i

각 복소 타입은 해당 실수 타입(float complex의 경우 float, double complex의 경우 double, long double complex의 경우 long double)의 두 요소로 된 배열과 같은 객체 표현과 정렬 요구를 가져요. 배열의 첫 요소는 실수 부분을, 두 번째 요소는 허수 성분을 담아요.

float a[4] = {1, 2, 3, 4};
float complex z1, z2;
memcpy(&z1, a, sizeof z1); // z1 becomes 1.0 + 2.0i
memcpy(&z2, a+2, sizeof z2); // z2 becomes 3.0 + 4.0i

복소수는 산술 연산자 + - */와 함께 쓸 수 있고, 허수·실수와 섞을 수도 있어요. <complex.h>에는 복소수를 위한 수학 함수가 여럿 정의돼 있어요. 내장 연산자와 라이브러리 함수 모두 math_errhandling에 서술된 대로 부동소수점 예외를 일으키고 errno를 설정할 수 있어요.

복소 타입에는 증가·감소 연산이 정의되지 않아요.

복소 타입에는 관계 연산자가 정의되지 않아요 ("작다"라는 개념이 없어요).

복소 타입과 다른 산술 타입 사이에는 암시적 변환이 정의돼 있어요.

복소수 산술의 하나-무한대(one-infinity) 모델을 지원하기 위해, C는 적어도 하나의 무한 부분을 가진 복소 값을 다른 부분이 NaN이어도 무한으로 간주하고, 모든 연산자와 함수가 무한의 기본 속성을 존중함을 보장하며, 모든 무한을 표준 무한(canonical one)으로 매핑하는 cproj를 제공해요 (정확한 규칙은 산술 연산자 문서 참고).

#include <complex.h>
#include <math.h>
#include <stdio.h>

int main(void)
{
    double complex z = (1 + 0*I) * (INFINITY + I*INFINITY);
//  textbook formula would give
//  (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN
//  but C gives a complex infinity
    printf("%f%+f*i\n", creal(z), cimag(z));

//  textbook formula would give
//  cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN
//  but C gives  ±∞+i*nan
    double complex y = cexp(INFINITY + I*NAN);
    printf("%f%+f*i\n", creal(y), cimag(y));
}

가능한 출력:

inf+inf*i 
inf+nan*i

C는 또한 직교 좌표 표현의 한계에도 불구하고 가능한 한 방향 정보를 보존하도록 여러 무한을 다뤄요. 허수 단위에 실수 무한을 곱하면 올바른 부호의 허수 무한이 돼요: i × ∞ = i∞. 또 i × (∞ – i∞) = ∞ + i∞는 합리적인 사분면을 나타내요.

허수 부동소수점 타입 (Imaginary floating types)

허수 부동소수점 타입은 수학의 허수, 즉 실수에 허수 단위를 곱한 bi로 쓸 수 있는 수를 모델링해요. 세 가지 허수 타입은 다음과 같아요.

  • float _Imaginary (<complex.h>를 포함하면 float imaginary로도 사용 가능)
  • double _Imaginary (double imaginary)
  • long double _Imaginary (long double imaginary)

참고: 모든 타입 지정자와 마찬가지로 어떤 순서도 허용돼요. long double imaginary, imaginary long double, double imaginary long은 같은 타입을 가리켜요.

#include <complex.h>
#include <stdio.h>

int main(void)
{
    double imaginary z = 3*I;
    z = 1 / z;
    printf("1/(3.0i) = %+.1fi\n", cimag(z));
}

출력:

1/(3.0i) = -0.3i

__STDC_IEC_559_COMPLEX__를 정의하는 구현은 허수 수를 지원하는 것이 권장되지만 필수는 아니에요. POSIX는 허수 수 지원을 식별하려면 매크로 _Imaginary_I가 정의되어 있는지 확인할 것을 권해요. (until C11)

__STDC_IEC_559_COMPLEX__(until C23)__STDC_IEC_60559_COMPLEX__(since C23)가 정의되면 허수 수가 지원돼요. (since C11)

세 허수 타입 각각은 대응하는 실수 타입(float imaginary의 경우 float, double imaginary의 경우 double, long double imaginary의 경우 long double)과 같은 객체 표현과 정렬 요구를 가져요.

참고: 그럼에도 허수 타입은 대응하는 실수 타입과 구별되고 호환되지 않아서 앨리어싱을 금지해요.

허수는 산술 연산자 + - */와 함께 쓸 수 있고, 복소·실수와 섞을 수도 있어요. <complex.h>에는 허수를 위한 수학 함수가 여럿 정의돼 있어요. 내장 연산자와 라이브러리 함수 모두 math_errhandling에 서술된 대로 부동소수점 예외를 일으키고 errno를 설정할 수 있어요.

허수 타입에는 증가·감소 연산이 정의되지 않아요.

허수 타입과 다른 산술 타입 사이에는 암시적 변환이 정의돼 있어요.

허수 수 덕분에 모든 복소수를 자연스러운 표기 x + I*y(I_Imaginary_I로 정의)로 표현할 수 있어요. 허수 타입이 없으면 어떤 특수 복소 값은 자연스럽게 만들 수 없어요. 예를 들어 I_Complex_I로 정의되면 0.0 + I*INFINITY는 실수 부분이 NaN이 되고, 대신 CMPLX(0.0, INFINITY)를 써야 해요. 음의 영 허수 성분을 가진 수(csqrt 같은 분기 절단이 있는 라이브러리 함수를 쓸 때 의미 있는)도 마찬가지예요. 1.0 - 0.0*II_Complex_I면 양의 영 허수 성분이 되고, 음의 영 허수 부분은 CMPLX 또는 conj를 써야 해요.

허수 타입은 구현도 단순하게 만들어줘요. 허수를 복소수에 곱하는 것은, 허수 타입이 지원되면 네 번의 곱셈과 두 번의 덧셈 대신 두 번의 곱셈으로 간단히 구현할 수 있어요. (since C99)

키워드

  • bool, true, false, char, int, short, long, signed, unsigned, float, double
  • _Bool, _BitInt, _Complex, _Imaginary, _Decimal32, _Decimal64, _Decimal128

값의 범위 (Range of values)

다음 표는 흔한 수치 표현의 한계에 대한 참고자료예요.

C23 이전에는 C 표준이 어떤 부호 있는 정수 표현도 허용했고, N비트 부호 있는 정수의 최소 보장 범위는 −(2^(N−1)−1)에서 +2^(N−1)−1까지였어요 (예: 부호 있는 8비트 타입의 −127에서 127). 이는 1의 보수나 부호-크기 표현의 한계에 대응해요.

하지만 모든 널리 쓰이는 데이터 모델(ILP32, LP32, LP64, LLP64 전부 포함)과 거의 모든 C 컴파일러는 2의 보수 표현을 쓰고, C23부터는 이것이 표준이 허용하는 유일한 표현이며 보장 범위는 −2^(N−1)에서 +2^(N−1)−1이에요 (예: 부호 있는 8비트 타입의 −128에서 127).

Type Size in bits Format Value range
Approximate Exact
character 8 signed −128 to 127
unsigned 0 to 255
16 UTF-16 0 to 65535
32 UTF-32 0 to 1114111 (0x10ffff)
integer 16 signed ± 3.27 · 10⁴ −32768 to 32767
unsigned 0 to 6.55 · 10⁴ 0 to 65535
32 signed ± 2.14 · 10⁹ −2,147,483,648 to 2,147,483,647
unsigned 0 to 4.29 · 10⁹ 0 to 4,294,967,295
64 signed ± 9.22 · 10¹⁸ −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned 0 to 1.84 · 10¹⁹ 0 to 18,446,744,073,709,551,615
binary floating-point 32 IEEE-754 min subnormal: ± 1.401,298,4 · 10⁻⁴⁵
min normal: ± 1.175,494,3 · 10⁻³⁸
max: ± 3.402,823,4 · 10³⁸
min subnormal: ±0x1p−149
min normal: ±0x1p−126
max: ±0x1.fffffep+127
64 IEEE-754 min subnormal: ± 4.940,656,458,412 · 10⁻³²⁴
min normal: ± 2.225,073,858,507,201,4 · 10⁻³⁰⁸
max: ± 1.797,693,134,862,315,7 · 10³⁰⁸
min subnormal: ±0x1p−1074
min normal: ±0x1p−1022
max: ±0x1.fffffffffffffp+1023
80 (참고 1) x86 min subnormal: ± 3.645,199,531,882,474,602,528 · 10⁻⁴⁹⁵¹
min normal: ± 3.362,103,143,112,093,506,263 · 10⁻⁴⁹³²
max: ± 1.189,731,495,357,231,765,021 · 10⁴⁹³²
min subnormal: ±0x1p−16445
min normal: ±0x1p−16382
max: ±0x1.fffffffffffffffep+16383
128 IEEE-754 min subnormal: ± 6.475,175,119,438,025,110,924,438,958,227,646,552,5 · 10⁻⁴⁹⁶⁶
min normal: ± 3.362,103,143,112,093,506,262,677,817,321,752,602,6 · 10⁻⁴⁹³²
max: ± 1.189,731,495,357,231,765,085,759,326,628,007,016,2 · 10⁴⁹³²
min subnormal: ±0x1p−16494
min normal: ±0x1p−16382
max: ±0x1.ffffffffffffffffffffffffffffp+16383
decimal floating-point 32 IEEE-754 min subnormal: ± 1 · 10⁻¹⁰¹
min normal: ± 1 · 10⁻⁹⁵
max: ± 9.999'999 · 10⁹⁶
64 IEEE-754 min subnormal: ± 1 · 10⁻³⁹⁸
min normal: ± 1 · 10⁻³⁸³
max: ± 9.999'999'999'999'999 · 10³⁸⁴
128 IEEE-754 min subnormal: ± 1 · 10⁻⁶¹⁷⁶
min normal: ± 1 · 10⁻⁶¹⁴³
max: ± 9.999'999'999'999'999'999'999'999'999'999'999 · 10⁶¹⁴⁴

참고 1: 객체 표현은 보통 32/64비트 플랫폼에서 각각 96/128비트를 차지해요.

참고: 실제(보장된 최소가 아닌) 범위는 라이브러리 헤더 <limits.h><float.h>에서 쓸 수 있어요.

더 알아보기

  • 타입 시스템의 전체 개요와 C 라이브러리의 타입 관련 유틸리티는 타입 문서에서 볼 수 있어요.
  • 같은 표현으로 다른 타입을 가리키는 typedef 이름은 관련 문서에서 다뤄요.
  • cppreference의 산술 타입 원문을 참고할 수 있어요.