HOWTO · C++
Usa l'operatore Modulo in C++
Questo articolo introduce come utilizzare l'operatore modulo in C++
In questa pagina
L’operatore % di C++ restituisce il resto della divisione tra interi. Per esempio, 10 % 3 dà 1. Funziona con i tipi integrali, compresi gli interi con segno, ma non accetta operandi in virgola mobile. Il divisore deve essere diverso da zero. Il risultato è utile per verificare la parità, calcolare posizioni cicliche e ottenere indici di bucket entro un intervallo, tenendo conto dei valori con segno quando necessario.
Quale risultato restituisce l’operatore % di C++
Per gli interi a e b, a % b è il resto della divisione a / b. In C++, la divisione intera tronca verso zero: quindi 10 / 3 dà 3 e 10 % 3 dà 1. Entrambi gli operandi devono avere un tipo integrale; se un operando non è integrale, l’espressione che usa % non è ben formata. Inoltre, il divisore deve essere diverso da zero, perché un divisore zero causa un comportamento indefinito.
Questo breve programma mostra un tipico calcolo con valori positivi:
#include <iostream>
int main() {
const int dividend = 17;
const int divisor = 5;
std::cout << dividend % divisor << '\n';
}
2
L’operatore % non è definito per i valori in virgola mobile. Se ti serve il resto di una divisione tra valori in virgola mobile, usa std::fmod di <cmath>.
Come funziona % con gli interi con segno
Con operandi con segno, la divisione intera tronca verso zero e, se il resto è diverso da zero, questo ha lo stesso segno del dividendo. Perciò -10 % 3 dà -1, mentre 10 % -3 dà 1; in entrambi i casi, il quoziente moltiplicato per il divisore più il resto è uguale al dividendo. Questo comportamento è specificato da C++ e non dipende dalla scelta del compilatore.
Poiché un resto negativo rimane negativo, % da solo non produce sempre un indice non negativo. Per ottenere un risultato normalizzato, usa un modulo positivo m, memorizza value % m in una variabile per il resto e aggiungi m solo se il resto è negativo. Poiché il resto è maggiore di -m, così si ottiene un valore nell’intervallo [0, m) senza una somma intermedia che possa causare overflow. Anche il quoziente deve essere rappresentabile: % causa un comportamento indefinito quando il quoziente della divisione intera corrispondente non è rappresentabile, per esempio quando si calcola il valore minimo di un tipo con segno % -1. In questo caso, che ha comportamento indefinito, l’identità sul resto riportata sopra non si applica.
Verificare la parità e scorrere ciclicamente i valori con %
Un intero è pari quando value % 2 == 0; altrimenti è dispari. Questo vale anche per gli interi negativi, perché un multiplo esatto di due ha resto zero.
Per usare indici ciclici, la dimensione della raccolta deve essere positiva e la posizione non negativa:
#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
L’array contiene tre elementi, quindi ogni indice è compreso tra 0 e 2; la sequenza delle posizioni si ripete a ogni iterazione del ciclo. Questo schema presuppone che la raccolta contenga almeno un elemento. Se le posizioni possono essere negative, normalizza il resto prima di usarlo come indice.
Usare i resti nell’hashing e negli intervalli di numeri casuali
Una tabella hash può usare un resto per mappare una chiave non negativa su uno dei bucket della tabella: key % tableSize. In questo caso, tableSize deve essere maggiore di zero. Se le chiavi possono essere negative, normalizza il risultato come descritto sopra prima di convertirlo in un indice. Una tabella hash reale richiede anche una funzione hash e una strategia per gestire le collisioni: il resto serve soltanto a selezionare un intervallo di bucket.
Per generare interi casuali, non dare per scontato che std::rand() % bound produca una distribuzione uniforme: se il numero di valori possibili del generatore non è un multiplo di bound, alcuni risultati si verificano più spesso. Se vuoi un intervallo intero inclusivo, è preferibile usare std::uniform_int_distribution con un motore casuale:
#include <iostream>
#include <random>
int main() {
std::mt19937 engine(12345);
std::uniform_int_distribution<int> range(1, 6);
std::cout << range(engine) << '\n';
}
6
I parametri della distribuzione definiscono i limiti inferiore e superiore inclusi. Con questo seme, questo motore e l’implementazione di std::uniform_int_distribution della libreria standard usata per la verifica, l’esempio stampa 6. C++ non richiede che la distribuzione mappi gli output del motore nello stesso modo tra diverse implementazioni della libreria standard: l’output e la sequenza esatti non sono quindi portabili e si ripetono solo se sono uguali sia il motore sia l’implementazione della distribuzione. Se ti servono sequenze diverse, scegli una sorgente per il seme adatta alla tua applicazione.
Usare std::modulus come oggetto funzione
std::modulus in <functional> è un oggetto funzione che applica % a due valori. È utile quando un algoritmo richiede un oggetto chiamabile invece di un’espressione con un operatore:
#include <functional>
#include <iostream>
int main() {
const std::modulus<int> remainder;
std::cout << remainder(17, 5) << '\n';
}
2
I suoi operandi seguono le stesse regole di %: devono essere integrali e il divisore deve essere diverso da zero. Nelle espressioni ordinarie, a % b è di solito la notazione più chiara.