算術演算子
算術演算子は被演算子に標準的な数学演算を適用します。
| This section is incomplete Reason: consider a more general-purpose ToC for this and other tables that cover multiple topics |
| 演算子 | 演算子の名前 | 例 | 結果 |
|---|---|---|---|
+
|
正号 | +a
|
a の昇格後の値。 |
-
|
負号 | -a
|
a の反数。 |
+
|
加算 | a + b
|
a と b の和。 |
-
|
減算 | a - b
|
a と b の差。 |
*
|
乗算 | a * b
|
a と b の積。 |
/
|
除算 | a / b
|
a を b で割った商。 |
%
|
剰余 | a % b
|
a を b で割った余り。 |
~
|
ビット単位の否定 | ~a
|
a のビット単位の否定。 |
&
|
ビット単位の論理積 | a & b
|
a と b のビット単位の論理積。 |
|
|
ビット単位の論理和 | a | b
|
a と b のビット単位の論理和。 |
^
|
ビット単位の排他的論理和 | a ^ b
|
a と b のビット単位の排他的論理和。 |
<<
|
ビット単位の左シフト | a << b
|
a を b ビット左にシフト。 |
>>
|
ビット単位の右シフト | a >> b
|
a を b ビット右にシフト。 |
目次
オーバーフロー
符号なし整数算術は常に modulo 2n
で行われます。 ただし n はその特定の整数のビット数です。 例えば unsigned int の場合、 UINT_MAX に 1 を足すと 0 になり、 0 から 1 を引くと UINT_MAX になります。
符号付き整数算術演算がオーバーフロー (結果が結果の型に収まらない) したとき、動作は未定義です。 その表現 (一般的には2の補数) のルールに従ってラップアラウンドするかもしれませんし、プラットフォームによってはまたはコンパイラオプション (例えば GCC や Clang の -ftrapv) によってはトラップが発生するかもしれませんし、コンパイラの最適化によって完全に削除されるかもしれません。
浮動小数点環境
#pragma STDC FENV_ACCESS が ON に設定されている場合、すべての浮動小数点算術演算は、現在の浮動小数点丸め方向に従い、 math_errhandling で指定されている通りに浮動小数点算術エラーを報告します (静的初期化の一部である場合は除きます (その場合、浮動小数点例外は発生せず、丸めモードは最も近いになります))。
浮動小数点の縮約
#pragma STDC FP_CONTRACT が OFF に設定されていない場合、すべての浮動小数点算術は中間結果が無限の範囲と精度を持っているかのように行われることがあります。 これは式が書かれた通りに正確に評価されたならば観察されたはずの丸め誤差や浮動小数点例外を省略する最適化です。 例えば、 (x*y) + z を単一の融合積和 CPU 命令で実装したり、 a = x*x*x*x; を tmp = x*x; a = tmp*tmp のように最適化したりすることを許可します。
縮約とは別に、浮動小数点算術の中間結果はその型が表すのと異なる範囲や精度を持つことがあります (FLT_EVAL_METHOD を参照してください)。
単項算術
単項算術演算子式は以下の形式を持ちます。
+ expression
|
(1) | |
- expression
|
(2) | |
正号と負号はどちらもまず被演算子に整数昇格を適用します。 その後、
- 正号は昇格後の値を返します。
- 負号は昇格後の値の反数を返します (ただし NaN の反数は NaN です)。
式の型は昇格後の型であり、値カテゴリは非左辺値です。
ノート
負号は、一般的なプラットフォーム (2の補数) では、 INT_MIN、 LONG_MIN、または LLONG_MIN に適用したとき、符号付き整数のオーバーフローにより、未定義動作を発生させます。
C++ では、単項演算子 + は、配列や関数など、他の組み込み型に対しても使用できますが、 C では、できません。
#include <stdio.h>
#include <complex.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);
printf("-1, where 1 is unsigned: %u\n", -1u);
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) | |
算術加減算
両方の被演算子が算術型の場合、
- まず、通常の算術変換が行われます。
- その後、被演算子の昇格後の値が数学の通常のルールに従って加算または減算されます (減算の場合は lhs から rhs を引きます)。 ただし、
- 一方の被演算子が 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 が配列の最初の要素を指すとき p-1 は未定義動作でありプラットフォームによっては失敗するかもしれないことに注意してください。
- ポインタ
P1がある配列のインデックスIの要素 (または最後の要素の次) を指していて、P2が同じ配列の要素 (または最後の要素の次) を指している場合、
P1-P2はJ-Iに等しい値と ptrdiff_t 型 (一般的には宣言可能な最も大きなオブジェクトのサイズの半分の、符号付き整数型) を持ちます。
結果が ptrdiff_t に収まる場合にのみ、動作は定義されます。
ポインタ算術の目的に対しては、配列の要素でないオブジェクトへのポインタはサイズ1の配列の最初の要素へのポインタとして扱われます。
// work in progress
int n = 4, m = 3;
int a[n][m]; // int 3個のVLA 4個のVLA
int (*p)[m] = a; // p == &a[0]
p = p + 1; // p == &a[1] (ポインタ算術は VLA に対しても同様に動作します)
(*p)[2] = 99; // a[1][2] を変更します。
乗法演算子
二項乗法算術演算子式は以下の形式を持ちます。
lhs * rhs
|
(1) | |
lhs / rhs
|
(2) | |
lhs % rhs
|
(3) | |
- まず、通常の算術変換が行われます。 その後...
乗算
二項演算子 * は (通常の算術変換の後) 被演算子の乗算を行います。 乗算は通常の算術の定義に従います。 ただし、
- 一方の被演算子が NaN の場合、結果は NaN です。
- 無限大とゼロを掛けると NaN になり、 FE_INVALID が発生します。
- 無限大と非ゼロを掛けると無限大になります (たとえ複素数の場合でも)。
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
除算
二項演算子 / は (通常の算術変換の後) 第1被演算子を第2被演算子で除算します。 除算は通常の算術の定義に従います。 ただし、
- 通常の算術変換の後の型が整数型のとき、結果は処理系定義の方向に丸めた (C99未満)ゼロに向かって切り捨てた (C99以上)代数的な商 (有理数ではない) です。
- 一方の被演算子が NaN の場合、結果は NaN です。
- 第1被演算子が複素無限大で第2被演算子が有限の場合、演算子 / の結果は複素無限大です。
- 第1被演算子が有限で第2被演算子が複素無限大の場合、演算子 / の結果はゼロです。
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)]/(u2
+v2
) のように計算することが許され、被演算子の範囲の制限および無限大への対処の責任はプログラマが負うものとみなされます。
過度のオーバーフローは許されないものの、複素数の除算はスピリアスな浮動小数点例外発生させることがあります (そうでなければオーバーフローしないバージョンを実装することは恐ろしく困難です)。
第2被演算子がゼロの場合、動作は未定義です。 ただし、 IEEE 浮動小数点算術がサポートされていて、複素数の除算が行われた場合は、
- 非ゼロの数値を ±0.0 で割ると正しい符号の無限大になり、 FE_DIVBYZERO が発生します。
- 0.0 を 0.0 で割ると NaN になり、 FE_INVALID が発生します。
剰余
二項演算子 % は (通常の算術変換の後) 第1被演算子を第2被演算子で割った余りを返します。
剰余の符号は、商 a/b が結果の型で表現可能な場合、 (a/b)*b + a%b == a が成り立つように定義されます。
第2被演算子がゼロの場合、動作は未定義です。
商 a/b が結果の型で表現可能でない場合、 a/b と a%b の動作はどちらも未定義です (これは2の補数のシステムでは INT_MIN%-1 が未定義であることを意味します)。
ノート: 剰余演算子は浮動小数点型に対しては動作しません。 ライブラリ関数 fmod がその機能を提供します。
ビット単位の論理演算
ビット単位の算術演算子式は以下の形式を持ちます。
~ rhs
|
(1) | |
lhs & rhs
|
(2) | |
lhs | rhs
|
(3) | |
lhs ^ rhs
|
(4) | |
ただし、
| lhs, rhs | - | 整数型の式。 |
まず、演算子 &、 ^、および | は両方の被演算子に通常の算術変換を行い、演算子 ~ はその唯一の被演算子に整数昇格を行います。
その後、対応する二項論理演算子がビット単位で適用されます。 つまり、結果の各ビットが、被演算子の対応するビットに適用された論理演算 (否定、論理積、論理和、排他的論理和) に従って、セットまたはクリアされます。
ノート: ビット単位の演算子はビット集合やビットマスクを操作するためによく使用されます。
ノート: 符号なし型 (昇格後) の場合、式 ~E は結果の型で表現可能な最も大きな値から E の元の値を引いた値と同等です。
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint16_t mask = 0x00f0;
uint32_t a = 0x12345678;
printf("Value: %#x mask: %#x\n"
"Setting bits: %#x\n"
"Clearing bits: %#x\n"
"Selecting bits: %#x\n",
a,mask,(a|mask),(a&~mask),(a&mask));
}
出力例:
Value: 0x12345678 mask: 0xf0
Setting bits: 0x123456f8
Clearing bits: 0x12345608
Selecting bits: 0x70
シフト演算子
ビット単位のシフト演算子式は以下の形式と同等です。
lhs << rhs
|
(1) | |
lhs >> rhs
|
(2) | |
ただし、
| lhs, rhs | - | 整数型の式。 |
まず、各被演算子に別々に整数昇格が行われます (ノート: これは通常の算術変換を行う他の二項算術演算子と異なります)。 結果の型は昇格後の lhs の型です。
rhs が負または昇格後の lhs のビット数より大きいまたは等しい場合、動作は未定義です。
符号なしの lhs の場合、 LHS << RHS の値は LHS * 2RHS
を戻り値の型の最大値プラス1のモジュロで縮小した値です (つまり、ビット単位の左シフトが行われ、結果の型から追い出されたビットは破棄されます)。 非負の値を持つ符号付きの lhs の場合、 LHS << RHS の値は lhs の昇格後の型で表現可能であれば LHS * 2RHS
であり、そうでなければ動作は未定義です。
符号なしの lhs の場合および非負の値を持つ符号付きの lhs の場合、 LHS >> RHS は LHS / 2RHS
の整数部です。 負の 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
参考文献
- C11 standard (ISO/IEC 9899:2011):
- 6.5.3.3 Unary arithmetic operators (p: 89)
- 6.5.5 Multiplicative operators (p: 92)
- 6.5.6 Additive operators (p: 92-94)
- 6.5.7 Bitwise shift operators (p: 94-95)
- 6.5.10 Bitwise AND operator (p: 97)
- 6.5.11 Bitwise exclusive OR operator (p: 98)
- 6.5.12 Bitwise inclusive OR operator (p: 98)
- C99 standard (ISO/IEC 9899:1999):
- 6.5.3.3 Unary arithmetic operators (p: 79)
- 6.5.5 Multiplicative operators (p: 82)
- 6.5.6 Additive operators (p: 82-84)
- 6.5.7 Bitwise shift operators (p: 84-85)
- 6.5.10 Bitwise AND operator (p: 87)
- 6.5.11 Bitwise exclusive OR operator (p: 88)
- 6.5.12 Bitwise inclusive OR operator (p: 88)
- C89/C90 standard (ISO/IEC 9899:1990):
- 3.3.3.3 Unary arithmetic operators
- 3.3.5 Multiplicative operators
- 3.3.6 Additive operators
- 3.3.7 Bitwise shift operators
- 3.3.10 Bitwise AND operator
- 3.3.11 Bitwise exclusive OR operator
- 3.3.12 Bitwise inclusive OR operator
関連項目
| 一般的な演算子 | ||||||
|---|---|---|---|---|---|---|
| 代入 | インクリメント デクリメント |
算術 | 論理 | 比較 | メンバアクセス | その他 |
|
|
|
|
|
|
|
|
算術演算子 の C++リファレンス
|