HOWTO · C++
在 C++ 中使用模數運算子
本文介紹瞭如何在 C++ 中使用 modulo 運算子。
本頁內容
C++ 的 % 運算子會傳回整數除法的餘數。例如,10 % 3 的結果是 1。它適用於整數型別,包括有號整數;浮點數不能作為運算元。除數必須非零。這個結果可用於判斷奇偶、循環取值及限制雜湊桶索引的範圍;處理有號值時,則須留意其正負號。
C++ % 運算子會傳回什麼
對整數 a 和 b 而言,a % b 是 a / b 後剩下的餘數。C++ 的整數除法會朝零截斷,因此 10 / 3 的結果是 3,而 10 % 3 的結果是 1。兩個運算元都必須是整數型別;若使用非整數運算元,該運算式便不符合 C++ 規則。除數也必須非零,因為除數為零會導致未定義行為。
以下小程式示範一般的正數運算:
#include <iostream>
int main() {
const int dividend = 17;
const int divisor = 5;
std::cout << dividend % divisor << '\n';
}
2
% 運算子不能用於浮點數。若需要計算浮點數的餘數,請使用 <cmath> 中的 std::fmod。
% 運算子如何處理有號整數
對有號運算元而言,整數除法會朝零截斷,而非零餘數的正負號會與被除數相同。因此,-10 % 3 的結果是 -1,而 10 % -3 的結果是 1;在這兩種情況下,商乘以除數再加上餘數,都會等於被除數。這是 C++ 規範所定義的行為,不由編譯器自行決定。
由於負餘數仍是負數,單獨使用 % 不一定能得到非負索引。若需要將結果正規化,請確保模數 m 為正數,將 value % m 存入餘數變數,並且只在餘數為負數時加上 m。因為餘數大於 -m,如此可將結果轉換到 [0, m) 範圍內,且不會在中間加總時發生溢位。商也必須能以其整數型別表示:若對應的整數除法商無法表示,% 會導致未定義行為,例如有號整數型別的最小值 % -1。上述餘數等式不適用於這種未定義的情況。
使用 % 判斷奇偶並循環取值
當 value % 2 == 0 時,整數為偶數;否則為奇數。負整數也適用此判斷,因為二的整數倍除以二時,餘數為零。
若要循環索引,請使用大於零的集合大小及非負的位置:
#include <iostream>
int main() {
const int values[] = {10, 20, 30};
const int count = static_cast<int>(sizeof(values) / sizeof(values[0]));
for (int position = 0; position < 5; ++position) {
const int index = position % count;
std::cout << values[index] << '\n';
}
}
10
20
30
10
20
陣列有三個元素,因此每個索引都介於 0 到 2;隨著迴圈執行,位置會不斷重複。這種寫法假設集合至少包含一個元素。若位置可能是負數,請先將餘數正規化,再將它用作索引。
使用餘數進行雜湊與產生隨機數範圍
雜湊表可以用餘數將非負鍵值映射到具有 tableSize 個雜湊桶的表格中:key % tableSize。這要求 tableSize > 0。若鍵值可能為負數,請先依照前述方式正規化結果,再將它轉換為索引。實際的雜湊表還需要雜湊函式及碰撞處理策略;餘數只負責選出雜湊桶的範圍。
產生隨機整數時,不要假設 std::rand() % bound 的分布均勻:如果產生器所有可能值的數量不是 bound 的倍數,某些結果出現的機率就會較高。若要取得含上下界的整數範圍,建議搭配隨機引擎使用 std::uniform_int_distribution:
#include <iostream>
#include <random>
int main() {
std::mt19937 engine(12345);
std::uniform_int_distribution<int> range(1, 6);
std::cout << range(engine) << '\n';
}
6
分布物件的參數會定義範圍中包含的下界與上界。在此種子、引擎及經測試的標準函式庫 std::uniform_int_distribution 實作下,範例會輸出 6。C++ 不要求不同標準函式庫實作以相同方式將引擎輸出映射到分布,因此確切的輸出及序列不具可攜性;只有在引擎和分布實作都相同時,結果才會重複。若需要不同的序列,請根據應用程式選擇合適的種子來源。
將 std::modulus 用作函式物件
<functional> 中的 std::modulus 是一個函式物件,會對兩個值套用 %。當演算法需要可呼叫物件,而不是運算子運算式時,它就派得上用場:
#include <functional>
#include <iostream>
int main() {
const std::modulus<int> remainder;
std::cout << remainder(17, 5) << '\n';
}
2
它的運算元遵循與 % 相同的整數型別及除數非零規則。一般運算式中,a % b 通常是最清楚的寫法。