Arithmetic operators
산술 연산자는 피연산자에 표준 수학 연산을 적용합니다.
|
이 섹션은 불완전합니다
이유: 이 표와 여러 주제를 다루는 다른 표들을 위해 더 일반적인 목차를 고려해 주세요 |
| 연산자 | 연산자 이름 | 예시 | 결과 |
|---|---|---|---|
| + | 단항 플러스 | + a | 승진 후 a 의 값 |
| - | 단항 마이너스 | - a | a 의 부정 |
| + | 덧셈 | a + b | a 와 b 의 합 |
| - | 뺄셈 | a - b | a 에서 b 를 뺀 값 |
| * | 곱셈 | a * b | a 와 b 의 곱 |
| / | 나눗셈 | a / b | a 를 b 로 나눈 값 |
| % | 나머지 | a % b | a 를 b 로 나눈 나머지 |
| ~ | 비트 NOT | ~a | a 의 비트 NOT |
| & | 비트 AND | a & b | a 와 b 의 비트 AND |
| | | 비트 OR | a | b | a 와 b 의 비트 OR |
| ^ | 비트 XOR | a ^ b | a 와 b 의 비트 XOR |
| << | 비트 왼쪽 시프트 | a << b | a 를 b 만큼 왼쪽 시프트 |
| >> | 비트 오른쪽 시프트 | a >> b | a 를 b 만큼 오른쪽 시프트 |
목차 |
오버플로우
부호 없는 정수 연산은 항상
modulo 2
n
으로 수행되며, 여기서 n은 해당 정수의 비트 수입니다. 예를 들어
unsigned
int
의 경우,
UINT_MAX
에 1을 더하면
0
이 되고,
0
에서 1을 빼면
UINT_MAX
가 됩니다.
부호 있는 정수 산술 연산에서 오버플로가 발생할 경우(결과가 해당 결과 타입에 맞지 않을 때), 그 동작은 정의되지 않습니다: 표현 방식의 규칙(일반적으로 2의 보수)에 따라 wraparound가 발생할 수 있으며, 일부 플랫폼이나 컴파일러 옵션(예: GCC와 Clang의
-ftrapv
)으로 인해 트랩이 발생할 수 있거나, 컴파일러에 의해 완전히
최적화되어 제거될 수 있습니다
.
부동소수점 환경
만약
#pragma STDC FENV_ACCESS
가
ON
으로 설정된 경우, 모든 부동 소수점 연산자는 현재 부동 소수점
반올림 방향
을 따르며,
math_errhandling
에 명시된 대로 부동 소수점 산술 오류를 보고합니다. 단,
정적 초기화
의 일부인 경우는 예외입니다(이 경우 부동 소수점 예외가 발생하지 않으며 반올림 모드는 가장 가까운 값으로 설정됩니다).
부동 소수점 축약
#pragma STDC FP_CONTRACT
가
OFF
로 설정되지 않는 한, 모든 부동 소수점 연산은 중간 결과가 무한한 범위와 정밀도를 가진 것처럼 수행될 수 있습니다. 즉, 표현식을 작성된 그대로 정확하게 평가했을 때 관찰될 수 있는 반올림 오차와 부동 소수점 예외를 생략하는 최적화가 허용됩니다. 예를 들어,
(
x
*
y
)
+
z
를 단일 FMA(Fused Multiply-Add) CPU 명령어로 구현하거나
a
=
x
*
x
*
x
*
x
;
를
tmp
=
x
*
x
;
a
=
tmp
*
tmp
로 최적화하는 것이 허용됩니다.
계약과 무관하게, 부동 소수점 연산의 중간 결과는 해당 타입이 나타내는 범위 및 정밀도와 다를 수 있습니다. 자세한 내용은 FLT_EVAL_METHOD 를 참조하십시오.
단항 연산
단항 산술 연산자 표현식의 형태는 다음과 같습니다
+
표현식
|
(1) | ||||||||
-
표현식
|
(2) | ||||||||
| expression | - | 모든 arithmetic type 의 표현식 |
단항 플러스와 단항 마이너스는 모두 먼저 피연산자에 정수 승격 을 적용한 후
- 단항 플러스는 승격 후의 값을 반환합니다
- 단항 마이너스는 승격 후 값의 음수를 반환합니다(NaN의 음수는 다른 NaN인 경우 제외)
표현식의 타입은 승진(promotion) 이후의 타입이며, 값 범주 는 비-좌측값(non-lvalue)입니다.
참고 사항
단항 마이너스 연산자는 일반적인(2의 보수) 플랫폼에서 INT_MIN , LONG_MIN , 또는 LLONG_MIN 에 적용될 때 부호 있는 정수 오버플로로 인해 정의되지 않은 동작을 유발합니다.
C++에서는 단항 연산자
+
를 배열과 함수 같은 다른 내장 타입과도 사용할 수 있지만, C에서는 그렇지 않습니다.
#include <stdio.h> #include <complex.h> #include <limits.h> int main(void) { char c = 'a'; printf("sizeof char: %zu sizeof int: %zu\n", sizeof c, sizeof +c); printf("-1, where 1 is signed: %d\n", -1); // 부호 없는 정수에 대해 산술 연산이 수행되므로 정의된 동작입니다. // 따라서 계산은 (-1) 모듈로 (2의 n승) = UINT_MAX가 되며, 여기서 n은 // unsigned int의 비트 수입니다. unsigned int가 32비트 길이라면 // (-1) 모듈로 (2의 32승) = 4294967295를 제공합니다 printf("-1, where 1 is unsigned: %u\n", -1u); // -INT_MIN의 수학적 값이 INT_MAX + 1이므로 정의되지 않은 동작 // (즉, 부호 있는 int의 최대 가능 값보다 1 더 큰 값) // // printf("%d\n", -INT_MIN); // -LONG_MIN의 수학적 값이 LONG_MAX + 1이므로 정의되지 않은 동작 // (즉, 부호 있는 long의 최대 가능 값보다 1 더 큰 값) // // printf("%ld\n", -LONG_MIN); // -LLONG_MIN의 수학적 값이 LLONG_MAX + 1이므로 정의되지 않은 동작 // (즉, 부호 있는 long long의 최대 가능 값보다 1 더 큰 값) // // printf("%lld\n", -LLONG_MIN); double complex z = 1 + 2*I; printf("-(1+2i) = %.1f%+.1f\n", creal(-z), cimag(-z)); }
가능한 출력:
sizeof char: 1 sizeof int: 4 -1, where 1 is signed: -1 -1, where 1 is unsigned: 4294967295 -(1+2i) = -1.0-2.0
덧셈 연산자
이항 덧셈 산술 연산자 표현식의 형식은 다음과 같습니다
lhs
+
rhs
|
(1) | ||||||||
lhs
-
rhs
|
(2) | ||||||||
-
- 둘 다 arithmetic types 을 가짐 (복소수 및 허수 포함)
- 하나는 완전 객체 타입에 대한 포인터이고 다른 하나는 정수 타입을 가짐
-
- 둘 다 arithmetic types (복소수 및 허수 포함)를 가져야 함
- lhs 가 완전 객체 타입에 대한 포인터를 가지고, rhs 가 정수 타입을 가져야 함
- 둘 다 한정자를 무시한 compatible 타입의 완전 객체에 대한 포인터여야 함
산술 덧셈과 뺄셈
두 피연산자 모두 arithmetic types 를 가지면,
- 먼저, usual arithmetic conversions 이 수행됩니다
- 그런 다음, 변환 후 피연산자들의 값은 수학의 일반적인 규칙에 따라 더하거나 빼집니다 (뺄셈의 경우, rhs 가 lhs 에서 빼집니다), 단
-
- 한 피연산자가 NaN이면 결과는 NaN입니다
- 무한대 빼기 무한대는 NaN이며 FE_INVALID 가 발생합니다
- 무한대 더하기 음의 무한대는 NaN이며 FE_INVALID 가 발생합니다
복소수와 허수의 덧셈 및 뺄셈은 다음과 같이 정의됩니다(두 피연산자가 모두 허수인 경우 결과 유형은 허수이고, 한 피연산자가 실수이고 다른 피연산자가 허수인 경우 복소수임을 유의하십시오. 이는 일반적인 산술 변환에 의해 지정됨):
| + 또는 - | u | iv | u + iv |
|---|---|---|---|
| x | x ± u | x ± iv | (x ± u) ± iv |
| iy | ±u + iy | i(y ± v) | ±u + i(y ± v) |
| x + iy | (x ± u) + iy | x + i(y ± v) | (x ± u) + i(y ± v) |
// work in progress // note: take part of the c/language/conversion example
포인터 연산
-
포인터
P가 인덱스I를 가진 배열의 요소를 가리키는 경우
-
-
P
+
N
와
N
+
P
는 동일한 배열에서
I+N인덱스를 가진 요소를 가리키는 포인터입니다 -
P
-
N
는 동일한 배열에서
I-N인덱스를 가진 요소를 가리키는 포인터입니다
-
P
+
N
와
N
+
P
는 동일한 배열에서
동작은 원본 포인터와 결과 포인터가 모두 동일한 배열의 요소를 가리키거나 해당 배열의 끝을 한 칸 지난 위치를 가리킬 때만 정의됩니다. p가 배열의 첫 번째 요소를 가리킬 때 p-1을 실행하는 것은 정의되지 않은 동작이며 일부 플랫폼에서 실패할 수 있습니다.
-
포인터
P1이 인덱스I(또는 끝의 다음)를 가진 배열 요소를 가리키고,P2가 동일한 배열의 인덱스J(또는 끝의 다음)를 가진 요소를 가리키는 경우,
-
- P1 - P2 는 I - J 와 동일한 값을 가지며, 타입은 ptrdiff_t 입니다 (이는 부호 있는 정수 타입으로, 일반적으로 선언 가능한 가장 큰 객체의 크기의 절반 정도입니다)
동작은 결과가 ptrdiff_t 에 맞는 경우에만 정의됩니다.
포인터 연산의 목적상, 어떤 배열의 요소가 아닌 객체에 대한 포인터는 크기가 1인 배열의 첫 번째 요소에 대한 포인터로 취급됩니다.
// work in progress int n = 4, m = 3; int a[n][m]; // VLA of 4 VLAs of 3 ints each int (*p)[m] = a; // p == &a[0] p = p + 1; // p == &a[1] (pointer arithmetic works with VLAs just the same) (*p)[2] = 99; // changes a[1][2]
승산 연산자
이항 곱셈 산술 연산자 표현식의 형식은 다음과 같습니다
lhs
*
rhs
|
(1) | ||||||||
lhs
/
rhs
|
(2) | ||||||||
lhs
%
rhs
|
(3) | ||||||||
태그 내부의 텍스트(C++ 연산자 기호), C++ 특정 용어(lhs, rhs)는 번역하지 않고 원본을 유지했습니다. 표의 구조와 서식도 완전히 보존되었습니다.
- 먼저, usual arithmetic conversions 이 수행됩니다. 그런 다음...
곱셈
이항 연산자 *는 피연산자들(일반적인 산술 변환 후)을 일반적인 산술 정의에 따라 곱셈을 수행합니다. 단, 다음 예외가 적용됩니다:
- 피연산자 중 하나가 NaN이면 결과는 NaN입니다
- 무한대와 0의 곱셈은 NaN을 생성하며 FE_INVALID 가 발생합니다
- 무한대와 0이 아닌 값의 곱셈은 무한대를 생성합니다 (복소수 인자의 경우에도 동일)
C 언어에서는 적어도 하나의 무한대 부분을 가진 모든 복소수 값 이 NaN을 포함하더라도 무한대로 간주되므로, 일반적인 산술 규칙이 복소수-복소수 곱셈에 적용되지 않습니다. 부동 소수점 연산자의 다른 조합은 다음 표를 따릅니다:
| * | u | iv | u + iv |
|---|---|---|---|
| x | xu | i(xv) | (xu) + i(xv) |
| iy | i(yu) | −yv | (−yv) + i(yu) |
| x + iy | (xu) + i(yu) | (−yv) + i(xv) | 특수 규칙 |
무한대 처리 외에도, 복소수 곱셈은 중간 결과가 오버플로되는 것을 허용하지 않습니다. 단,
#pragma STDC CX_LIMITED_RANGE
가
ON
으로 설정된 경우는 예외이며, 이 경우 값은
(x+iy)×(u+iv) = (xu-yv)+i(yu+xv)
와 같이 계산될 수 있습니다. 이때 프로그래머는 피연산자의 범위를 제한하고 무한대를 처리할 책임을 집니다.
부당한 오버플로를 허용하지 않음에도 불구하고, 복소수 곱셈은 잘못된 부동 소수점 예외를 발생시킬 수 있습니다 (그렇지 않으면 오버플로가 발생하지 않는 버전을 구현하는 것이 극도로 어렵습니다)
#include <stdio.h> #include <stdio.h> #include <complex.h> #include <math.h> int main(void) { // TODO simpler cases, take some from C++ double complex z = (1 + 0*I) * (INFINITY + I*INFINITY); // 교과서 공식으로는 // (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN // 이지만 C는 복소수 무한대를 제공함 printf("%f + i*%f\n", creal(z), cimag(z)); // 교과서 공식으로는 // cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN // 이지만 C는 ±∞+i*nan을 제공함 double complex y = cexp(INFINITY + I*NAN); printf("%f + i*%f\n", creal(y), cimag(y)); }
가능한 출력:
inf + i*inf inf + i*nan
나눗셈
이항 연산자
/
는 첫 번째 피연산자를 두 번째 피연산자로 나누는데(일반적인 산술 변환 후), 다음 예외를 제외하고 일반적인 산술 정의를 따릅니다:
- 일반적인 산술 변환 후의 타입이 정수형일 때, 결과는 대수적 몫(분수가 아님)이며, 구현에서 정의된 방향으로 반올림됨 (C99 이전) 영을 향해 절사됨 (C99 이후)
- 한 피연산자가 NaN인 경우 결과는 NaN임
-
첫 번째 피연산자가 복소수 무한대이고 두 번째 피연산자가 유한한 경우,
/연산자의 결과는 복소수 무한대임 -
첫 번째 피연산자가 유한하고 두 번째 피연산자가 복소수 무한대인 경우,
/연산자의 결과는 영임
C 언어에서는 복소수 값 중 적어도 한 부분이 무한대인 경우 다른 부분이 NaN이더라도 항상 무한대로 간주되므로, 일반적인 산술 규칙이 복소수-복소수 나눗셈에는 적용되지 않습니다. 부동 소수점 연산자의 다른 조합은 다음 표를 따릅니다:
| / | u | iv |
|---|---|---|
| x | x/u | i(−x/v) |
| iy | i(y/u) | y/v |
| x + iy | (x/u) + i(y/u) | (y/v) + i(−x/v) |
무한대 처리 외에도, 복소수 나눗셈은 중간 결과가 오버플로되는 것을 허용하지 않습니다. 단,
#pragma STDC CX_LIMITED_RANGE
가
ON
으로 설정된 경우에는 예외이며, 이 경우 값은
(x+iy)/(u+iv) = [(xu+yv)+i(yu-xv)]/(u
2
+v
2
)
와 같이 계산될 수 있습니다. 이때 프로그래머는 피연산자의 범위를 제한하고 무한대를 처리할 책임을 집니다.
부당한 오버플로를 허용하지 않음에도 불구하고, 복소수 나눗셈은 허위 부동 소수점 예외를 발생시킬 수 있습니다 (그렇지 않으면 오버플로가 발생하지 않는 버전을 구현하는 것이 극도로 어렵습니다)
두 번째 피연산자가 0인 경우, 동작은 정의되지 않습니다. 단, IEEE 부동 소수점 연산이 지원되고 부동 소수점 나눗셈이 수행되는 경우에는 다음이 적용됩니다:
- 0이 아닌 숫자를 ±0.0으로 나누면 부호가 맞는 무한대가 생성되고 FE_DIVBYZERO 가 발생합니다
- 0.0을 0.0으로 나누면 NaN이 생성되고 FE_INVALID 가 발생합니다
나머지
이항 연산자 %는 첫 번째 피연산자를 두 번째 피연산자로 나눈 나머지를 반환합니다(일반적인 산술 변환 후).
나머지의 부호는 몫
a/b
가 결과 타입으로 표현 가능할 경우,
(
a
/
b
)
*
b
+
a
%
b
==
a
가 성립하도록 정의됩니다.
두 번째 피연산자가 0인 경우, 동작은 정의되지 않습니다.
몫
a/b
가 결과 타입으로 표현 불가능한 경우,
a/b
와
a%b
의 동작은 모두 정의되지 않음 (이는
INT_MIN
%-
1
가 2의 보수 시스템에서 정의되지 않음을 의미함)
참고: 나머지 연산자는 부동 소수점 타입에서 작동하지 않으며, 이 기능은 라이브러리 함수 fmod 에서 제공됩니다.
비트 논리 연산
비트 연산자 표현식의 형식은 다음과 같습니다
~
rhs
|
(1) | ||||||||
lhs
&
rhs
|
(2) | ||||||||
lhs
|
rhs
|
(3) | ||||||||
lhs
^
rhs
|
(4) | ||||||||
`, `
`, `
여기서
| lhs , rhs | - | 정수형 표현식 |
먼저, 연산자 & , ^ , 그리고 | 는 두 피연산자에 대해 usual arithmetic conversions 를 수행하고, 연산자 ~ 는 단일 피연산자에 대해 integer promotions 를 수행합니다.
그런 다음, 해당하는 이진 논리 연산자들이 비트 단위로 적용됩니다; 즉, 결과의 각 비트는 피연산자들의 해당 비트에 적용된 논리 연산(NOT, AND, OR, 또는 XOR)에 따라 설정되거나 지워집니다.
참고: 비트 연산자는 일반적으로 비트 집합과 비트 마스크를 조작하는 데 사용됩니다.
참고: 부호 없는 타입(승진 후)의 경우, 표현식 ~E 는 결과 타입으로 표현 가능한 최댓값에서 원래 E 의 값을 뺀 것과 동일합니다.
가능한 출력:
Promoted mask: 0x000000f0 Value: 0x12345678 Setting bits: 0x123456f8 Clearing bits: 0x12345608 Selecting bits: 0x00000070
시프트 연산자
비트 시프트 연산자 표현식의 형식은 다음과 같습니다
lhs
<<
rhs
|
(1) | ||||||||
lhs
>>
rhs
|
(2) | ||||||||
태그 내부의 C++ 연산자(<<, >>)와 lhs/rhs 같은 C++ 용어는 번역하지 않고 원형을 유지했습니다. 표의 구조와 서식도 완전히 보존되었습니다.
여기서
| lhs , rhs | - | 정수형 표현식 |
먼저, integer promotions 이 각 피연산자에 대해 개별적으로 수행됩니다 (참고: 이는 일반 산술 변환을 수행하는 다른 이항 산술 연산자들과 다릅니다). 결과의 타입은 승격 후 lhs 의 타입입니다.
rhs 가 음수이거나 승격된 lhs 의 비트 수보다 크거나 같은 경우 동작은 정의되지 않습니다.
부호 없는
lhs
의 경우,
LHS << RHS
의 값은
LHS * 2
RHS
의 값을 반환 타입의 최대값에 1을 더한 값으로 모듈로 감소시킨 것입니다(즉, 비트 왼쪽 시프트가 수행되고 대상 타입에서 벗어난 비트들은 버려집니다). 음수가 아닌 값을 가진 부호 있는
lhs
의 경우,
LHS << RHS
의 값은
LHS * 2
RHS
가
lhs
의 승격된 타입으로 표현 가능할 때 해당 값이며, 그렇지 않으면 동작은 정의되지 않습니다.
부호 없는
lhs
와 음수가 아닌 값을 가진 부호 있는
lhs
의 경우,
LHS >> RHS
의 값은
LHS / 2
RHS
의 정수 부분입니다. 음수인
LHS
의 경우,
LHS >> RHS
의 값은 구현에 따라 정의되며, 대부분의 구현에서 이는 산술 우측 시프트를 수행합니다(결과가 음수로 유지되도록). 따라서 대부분의 구현에서 부호 있는
LHS
를 우측 시프트할 때 새로운 상위 비트들은 원본의 부호 비트로 채워집니다(즉, 음수가 아니었으면 0으로, 음수였으면 1로 채워짐).
#include <stdio.h> enum {ONE=1, TWO=2}; int main(void) { char c = 0x10; unsigned long long ulong_num = 0x123; printf("0x123 << 1 = %#llx\n" "0x123 << 63 = %#llx\n" // 오버플로우 시 부호 없는 숫자의 상위 비트가 잘림 "0x10 << 10 = %#x\n", // char가 int로 승격됨 ulong_num << 1, ulong_num << 63, c << 10); long long long_num = -1000; printf("-1000 >> 1 = %lld\n", long_num >> ONE); // 구현 정의 동작 }
가능한 출력:
0x123 << 1 = 0x246 0x123 << 63 = 0x8000000000000000 0x10 << 10 = 0x4000 -1000 >> 1 = -500
참고문헌
- C17 표준 (ISO/IEC 9899:2018):
-
- 6.5.3.3 단항 산술 연산자 (p: 64)
-
- 6.5.5 승법 연산자 (p: 66)
-
- 6.5.6 가법 연산자 (p: 66-68)
-
- 6.5.7 비트 시프트 연산자 (p: 68)
-
- 6.5.10 비트 AND 연산자 (p: 70)
-
- 6.5.11 비트 배타적 OR 연산자 (p: 70)
-
- 6.5.12 비트 포괄적 OR 연산자 (p: 70-71)
- C11 표준 (ISO/IEC 9899:2011):
-
- 6.5.3.3 단항 산술 연산자 (p: 89)
-
- 6.5.5 승법 연산자 (p: 92)
-
- 6.5.6 가법 연산자 (p: 92-94)
-
- 6.5.7 비트 시프트 연산자 (p: 94-95)
-
- 6.5.10 비트 AND 연산자 (p: 97)
-
- 6.5.11 비트 배타적 OR 연산자 (p: 98)
-
- 6.5.12 비트 포괄적 OR 연산자 (p: 98)
- C99 표준 (ISO/IEC 9899:1999):
-
- 6.5.3.3 단항 산술 연산자 (p: 79)
-
- 6.5.5 승법 연산자 (p: 82)
-
- 6.5.6 가법 연산자 (p: 82-84)
-
- 6.5.7 비트 시프트 연산자 (p: 84-85)
-
- 6.5.10 비트 AND 연산자 (p: 87)
-
- 6.5.11 비트 배타적 OR 연산자 (p: 88)
-
- 6.5.12 비트 포괄적 OR 연산자 (p: 88)
- C89/C90 표준 (ISO/IEC 9899:1990):
-
- 3.3.3.3 단항 산술 연산자
-
- 3.3.5 승법 연산자
-
- 3.3.6 가법 연산자
-
- 3.3.7 비트 시프트 연산자
-
- 3.3.10 비트 AND 연산자
-
- 3.3.11 비트 배타적 OR 연산자
-
- 3.3.12 비트 포괄적 OR 연산자
참고 항목
| 일반 연산자 | ||||||
|---|---|---|---|---|---|---|
| 대입 |
증가
감소 |
산술 | 논리 | 비교 |
멤버
접근 |
기타 |
|
a
=
b
|
++
a
|
+
a
|
!
a
|
a
==
b
|
a
[
b
]
|
a
(
...
)
|
|
C++ 문서
for
Arithmetic operators
|