HOWTO · C++

C++ でモジュロ演算子を使用する

この記事では、C++ での modulo 演算子の使い方を紹介します。

このページの内容

C++ の % 演算子は、整数除算で生じる余りを返します。たとえば、10 % 3 は 1 です。符号付き整数を含む整数型で使えますが、浮動小数点数のオペランドには使えません。除数は 0 以外でなければなりません。符号に注意しながら、偶奇の判定、位置の循環、範囲内のバケット番号の決定などに利用できます。

C++ の % 演算子が返す値

整数 a と b に対して、a % b は a / b の余りです。C++ の整数除算では小数部分を 0 に向かって切り捨てるため、10 / 3 は 3、10 % 3 は 1 になります。両方のオペランドは整数型でなければなりません。整数型でないオペランドを % に渡すと、式は不適格となります。また、除数は 0 以外でなければならず、除数が 0 の場合は動作が未定義です。

次の短いプログラムは、正の値を使った一般的な計算例です。

#include <iostream>

int main() {
    const int dividend = 17;
    const int divisor = 5;
    std::cout << dividend % divisor << '\n';
}
2

% 演算子は浮動小数点数には定義されていません。浮動小数点数の余りが必要な場合は、<cmath> の std::fmod を使います。

符号付き整数での % の動作

符号付きオペランドの場合、整数除算では小数部分を 0 に向かって切り捨て、0 でない余りの符号は被除数と同じになります。そのため、-10 % 3 は -1、10 % -3 は 1 です。どちらの場合も、商に除数を掛けて余りを足すと被除数になります。この動作は C++ の仕様で定められており、コンパイラーの選択に委ねられているわけではありません。

負の余りは負のままなので、% だけでは常に 0 以上のインデックスになるとは限りません。結果を正規化するには、法 m が正であることを条件とし、value % m を余り用の変数に格納して、その余りが負の場合にだけ m を加えます。余りは -m より大きいため、この方法なら途中の加算でオーバーフローすることなく、結果を [0, m) の範囲にできます。また、商が表現可能である必要があります。対応する整数除算の商を表現できない場合、たとえば符号付き型の最小値 % -1 では % の動作は未定義です。この未定義のケースには、上記の余りの恒等式は当てはまりません。

% で偶奇を判定し、値を循環させる

整数は、value % 2 == 0 なら偶数、それ以外なら奇数です。2 の倍数の余りは 0 なので、負の整数にも使えます。

循環するインデックスを求めるには、コレクションのサイズが正で、位置が 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

配列には要素が 3 つあるため、各インデックスは 0 から 2 までです。ループが進むにつれて位置の並びが繰り返されます。この方法は、コレクションに少なくとも 1 つ要素があることを前提としています。位置が負になる可能性がある場合は、インデックスとして使う前に余りを正規化してください。

ハッシュ処理と乱数の範囲指定で余りを使う

ハッシュテーブルでは、余りを使って 0 以上のキーを 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 は、2 つの値に % を適用する関数オブジェクトです。演算子式ではなく呼び出し可能なオブジェクトを必要とするアルゴリズムで役立ちます。

#include <functional>
#include <iostream>

int main() {
    const std::modulus<int> remainder;
    std::cout << remainder(17, 5) << '\n';
}
2

オペランドには % と同じ整数型および 0 以外の除数という規則が適用されます。通常の式では、a % b と書くのが最も分かりやすいでしょう。