HOWTO · C++

Den Modulo-Operator in C++ verwenden

Dieser Artikel beschreibt, wie der Modulo-Operator in C++ verwendet wird

Auf dieser Seite

Der C++-Operator % liefert den Rest einer ganzzahligen Division. Zum Beispiel ergibt 10 % 3 den Wert 1. Er funktioniert mit ganzzahligen Typen, darunter vorzeichenbehafteten Ganzzahlen; Gleitkommaoperanden sind nicht zulässig. Der Divisor darf nicht null sein. Das Ergebnis eignet sich für Paritätsprüfungen, zyklische Positionen und begrenzte Bucket-Indizes. Bei vorzeichenbehafteten Werten ist dabei gegebenenfalls besondere Vorsicht nötig.

Was der C++-Operator % zurückgibt

Für Ganzzahlen a und b ist a % b der Rest, der nach a / b übrig bleibt. C++ rundet bei der ganzzahligen Division in Richtung null ab: 10 / 3 ergibt also 3 und 10 % 3 ergibt 1. Beide Operanden müssen ganzzahlige Typen haben; wird % mit einem nicht-ganzzahligen Operanden verwendet, ist der Ausdruck nicht wohlgeformt. Außerdem muss der Divisor ungleich null sein, denn ein Divisor von null führt zu undefiniertem Verhalten.

Dieses kleine Programm zeigt eine typische Berechnung mit positiven Zahlen:

#include <iostream>

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

Der Operator % ist für Gleitkommawerte nicht definiert. Wenn Sie einen Rest für Gleitkommawerte benötigen, verwenden Sie std::fmod aus <cmath>.

Wie % mit vorzeichenbehafteten Ganzzahlen funktioniert

Bei vorzeichenbehafteten Operanden wird die ganzzahlige Division in Richtung null abgerundet. Ein von null verschiedener Rest hat dasselbe Vorzeichen wie der Dividend. Daher ergibt -10 % 3 den Wert -1, während 10 % -3 den Wert 1 ergibt. In beiden Fällen gilt: Quotient mal Divisor plus Rest ist gleich dem Dividend. Dieses Verhalten ist in C++ festgelegt und hängt nicht von der Wahl des Compilers ab.

Da ein negativer Rest negativ bleibt, liefert % allein nicht immer einen nicht-negativen Index. Wenn Sie ein normalisiertes Ergebnis benötigen, verwenden Sie einen positiven Modulus m, speichern Sie value % m in einer Restvariable und addieren Sie m nur dann, wenn dieser Rest negativ ist. Da der Rest größer als -m ist, liegt das Ergebnis anschließend im Bereich [0, m), ohne dass eine Zwischensumme überlaufen kann. Auch der Quotient muss darstellbar sein: % führt zu undefiniertem Verhalten, wenn der Quotient der entsprechenden ganzzahligen Division nicht darstellbar ist, etwa beim kleinsten Wert eines vorzeichenbehafteten Typs % -1. Für diesen Fall mit undefiniertem Verhalten gilt die obige Restgleichung nicht.

Parität prüfen und Werte mit % zyklisch durchlaufen

Eine Ganzzahl ist gerade, wenn value % 2 == 0 gilt; andernfalls ist sie ungerade. Das funktioniert auch mit negativen Ganzzahlen, denn ein ganzzahliges Vielfaches von zwei hat den Rest null.

Für eine zyklische Indizierung benötigen Sie eine positive Größe der Sammlung und eine nicht-negative Position:

#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

Das Array enthält drei Elemente, daher liegt jeder Index zwischen 0 und 2; während die Schleife fortschreitet, wiederholt sich die Positionsfolge. Dieses Muster setzt voraus, dass die Sammlung mindestens ein Element enthält. Wenn Positionen negativ sein können, normalisieren Sie den Rest, bevor Sie ihn als Index verwenden.

Reste beim Hashing und für Zufallszahlen in einem Wertebereich verwenden

Eine Hash-Tabelle kann mit einem Rest einen nicht-negativen Schlüssel auf einen Tisch mit tableSize Buckets abbilden: key % tableSize. Dafür muss tableSize > 0 gelten. Wenn Schlüssel negativ sein können, normalisieren Sie das Ergebnis wie oben beschrieben, bevor Sie es in einen Index umwandeln. Reale Hash-Tabellen benötigen außerdem eine Hash-Funktion und eine Strategie für Kollisionen; der Rest wählt lediglich einen Bucket aus dem Wertebereich aus.

Bei Zufallszahlen sollten Sie nicht davon ausgehen, dass std::rand() % bound gleichmäßig verteilt ist: Ist die Anzahl möglicher Generatorwerte kein Vielfaches von bound, treten manche Ergebnisse häufiger auf. Wenn Sie einen inklusiven Ganzzahlbereich benötigen, verwenden Sie vorzugsweise std::uniform_int_distribution mit einem Zufallszahlengenerator:

#include <iostream>
#include <random>

int main() {
    std::mt19937 engine(12345);
    std::uniform_int_distribution<int> range(1, 6);
    std::cout << range(engine) << '\n';
}
6

Die Parameter der Verteilung legen die eingeschlossene Unter- und Obergrenze fest. Mit diesem Seed, diesem Generator und der getesteten Implementierung der Standardbibliothek für std::uniform_int_distribution gibt das Beispiel 6 aus. C++ schreibt nicht vor, dass die Verteilung Generatorwerte in verschiedenen Standardbibliotheksimplementierungen identisch abbildet. Deshalb sind die genaue Ausgabe und die Zahlenfolge nicht portabel; sie lassen sich nur dann reproduzieren, wenn sowohl der Generator als auch die Implementierung der Verteilung identisch sind. Wenn Sie andere Zahlenfolgen benötigen, wählen Sie eine für Ihre Anwendung geeignete Seed-Quelle.

std::modulus als Funktionsobjekt verwenden

std::modulus aus <functional> ist ein Funktionsobjekt, das % auf zwei Werte anwendet. Es ist nützlich, wenn ein Algorithmus ein aufrufbares Objekt statt eines Operatorausdrucks erwartet:

#include <functional>
#include <iostream>

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

Für seine Operanden gelten dieselben Regeln zu ganzzahligen Typen und einem Divisor ungleich null wie für %. Bei gewöhnlichen Ausdrücken ist a % b meist die klarste Schreibweise.