算術運算子
算術運算子對其運算元執行標準數學運算。
本節不完整 原因:考慮為此表和涵蓋多個主題的其他表使用更通用的目錄 |
運算子 | 運算子名稱 | 示例 | 結果 |
---|---|---|---|
+ | 一元加 | +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 位 |
目錄 |
[編輯] 溢位
無符號整數算術總是按 模 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
,所有浮點算術可以執行,就好像中間結果具有無限的範圍和精度,即省略舍入錯誤和浮點異常的最佳化,這些錯誤和異常如果表示式完全按書面形式評估則會觀察到。例如,允許透過單個融合乘加 CPU 指令實現 (x*y) + z,或者將 a = x*x*x*x; 最佳化為 tmp = x*x; a = tmp*tmp。
與收縮無關,浮點算術的中間結果的範圍和精度可能與其型別指示的不同,請參閱 FLT_EVAL_METHOD
[編輯] 一元算術
一元算術運算子表示式具有以下形式
+ 表示式 |
(1) | ||||||||
- 表示式 |
(2) | ||||||||
表示式 | - | 任何算術型別的表示式 |
一元加和一元減都首先對其運算元應用整數提升,然後
- 一元加返回提升後的值
- 一元減返回提升後值的負值(除了 NaN 的負值是另一個 NaN)
表示式的型別是提升後的型別,值類別是非左值。
[編輯] 注意
在典型的(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); // Defined behavior since arithmetic is performed for unsigned integer. // Hence, the calculation is (-1) modulo (2 raised to n) = UINT_MAX, where n is // the number of bits of unsigned int. If unsigned int is 32-bit long, then this // gives (-1) modulo (2 raised to 32) = 4294967295 printf("-1, where 1 is unsigned: %u\n", -1u); // Undefined behavior because the mathematical value of -INT_MIN = INT_MAX + 1 // (i.e. 1 more than the maximum possible value for signed int) // // printf("%d\n", -INT_MIN); // Undefined behavior because the mathematical value of -LONG_MIN = LONG_MAX + 1 // (i.e. 1 more than the maximum possible value for signed long) // // printf("%ld\n", -LONG_MIN); // Undefined behavior because the mathematical value of -LLONG_MIN = LLONG_MAX + 1 // (i.e. 1 more than the maximum possible value for signed long long) // // 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) | ||||||||
[編輯] 算術加法和減法
如果兩個運算元都具有算術型別,則
- 首先,執行常用算術轉換
- 然後,轉換後的運算元的值按照通常的數學規則進行加法或減法(對於減法,將 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) | ||||||||
- 首先,執行常用算術轉換。然後...
[編輯] 乘法
二元運算子 * 執行其運算元(在常用算術轉換後)的乘法,遵循通常的算術定義,除了
- 如果一個運算元是 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); // textbook formula would give // (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN // but C gives a complex infinity printf("%f + i*%f\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 + 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)]/(u2
+v2
) 計算一樣,因為程式設計師承擔了限制運算元範圍和處理無窮大的責任。
儘管不允許不必要的溢位,複數除法可能引發虛假的浮點異常(否則,實現非溢位版本將極其困難)
如果第二個運算元為零,則行為未定義,除非支援 IEEE 浮點算術,並且正在進行浮點除法,則
- 非零數除以 ±0.0 得到正確符號的無窮大並引發 FE_DIVBYZERO
- 0.0 除以 0.0 得到 NaN 並引發 FE_INVALID
[編輯] 餘數
二元運算子 % 產生第一個運算元除以第二個運算元(在常用算術轉換後)的餘數。
餘數的符號定義為:如果商 a/b
在結果型別中可表示,則 (a/b)*b + a%b == a。
如果第二個運算元為零,則行為未定義。
如果商 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 的原始值。
可能的輸出
Promoted mask: 0x000000f0 Value: 0x12345678 Setting bits: 0x123456f8 Clearing bits: 0x12345608 Selecting bits: 0x00000070
[編輯] 移位運算子
按位移位運算子表示式具有以下形式
lhs << rhs |
(1) | ||||||||
lhs >> rhs |
(2) | ||||||||
其中
lhs, rhs | - | 整數型別的表示式 |
首先,對每個運算元單獨執行整數提升(注意:這與其他所有執行常用算術轉換的二元算術運算子不同)。結果的型別是 lhs 提升後的型別。
如果 rhs 為負數或大於或等於提升後的 lhs 中的位數,則行為未定義。
對於無符號 lhs,LHS << RHS
的值是 LHS * 2RHS
的值,對返回型別的最大值加 1 取模(即執行按位左移,並丟棄移出目標型別的位)。對於具有非負值的有符號 lhs,如果 LHS * 2RHS
在 lhs 的提升型別中可表示,則 LHS << RHS
的值為 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" // overflow truncates high bits for unsigned numbers "0x10 << 10 = %#x\n", // char is promoted to int ulong_num << 1, ulong_num << 63, c << 10); long long long_num = -1000; printf("-1000 >> 1 = %lld\n", long_num >> ONE); // implementation defined }
可能的輸出
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 按位與運算子 (p: 70)
- 6.5.11 按位異或運算子 (p: 70)
- 6.5.12 按位或運算子 (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 按位與運算子 (p: 97)
- 6.5.11 按位異或運算子 (p: 98)
- 6.5.12 按位或運算子 (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 按位與運算子 (p: 87)
- 6.5.11 按位異或運算子 (p: 88)
- 6.5.12 按位或運算子 (p: 88)
- C89/C90 標準 (ISO/IEC 9899:1990)
- 3.3.3.3 一元算術運算子
- 3.3.5 乘性運算子
- 3.3.6 加性運算子
- 3.3.7 按位移位運算子
- 3.3.10 按位與運算子
- 3.3.11 按位異或運算子
- 3.3.12 按位或運算子
[編輯] 另請參閱
常見運算子 | ||||||
---|---|---|---|---|---|---|
賦值 | 遞增 遞減 |
算術 | 邏輯 | 比較 | 成員 訪問 |
其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
C++ 文件 關於 算術運算子
|