Potenze e moduli
Ci sono vari
algoritmi, perlopiù
nell’ambito della crittografia, che richiedono di calcolare delle potenze in
aritmetica modulare. Senza entrare troppo nel contesto, prendiamo subito un
esempio: per due interi positivi a e p piuttosto grandi, vogliamo calcolare il modulo
seguente, con dei numeri a 32 bit (uint32_t
).
Facciamo finta che 53 e 91 siano numeri grandi, per cui vogliamo calcolare
è un numero astronomico, che richiederebbe oltre 500 bit per essere rappresentato, per cui bisogna trovare un modo indiretto per ottenerne il valore modulo 91. Il primo algoritmo che potrebbe venire a mente è questo, che esegue delle moltiplicazioni consecutive e ne prende il modulo ad ogni iterazione.
// supponiamo mod > 1, tralasciamo 0^0
uint32_t potenza_modulo(uint32_t base, uint32_t esp, uint32_t mod) {
base %= mod;
if (base == 0) return 0;
uint32_t risultato = 1;
for (uint32_t i = 0; i < esp; ++i) {
risultato = (risultato * base) % mod;
}
return risultato;
}
Qui bisogna tenere a mente che risultato * base
potrebbe non entrare in 32
bit. Siccome sia risultato
che base
possono essere al massimo mod - 1
,
basta verificare all’inizio che (mod - 1) * (mod - 1)
sia rappresentabile.
Ma non esiste un algoritmo più efficiente? Sì: su questa pagina trovate dello pseudocodice, ma proverò a spiegarlo qui sotto.
uint32_t potenza_modulo(uint32_t base, uint32_t esp, uint32_t mod) {
base %= mod;
if (base == 0) return 0;
uint32_t risultato = 1;
while (true) {
if (esp & 1) risultato = (risultato * base) % mod;
esp >>= 1;
if (esp == 0) break;
base = (base * base) % mod;
}
return risultato;
}
Il trucco sta nel convertire l’esponente (91) in binario (1011011
):
Possiamo quindi accumulare il risultato moltiplicandolo per il valore
corrispondente ad ogni bit 1
dell’esponente, saltando gli zeri. Guardiamo meglio il contenuto
del ciclo while
:
if (esp & 1)
: se l’esponente è dispari, moltiplichiamo il risultato per la base, ricordando chea * b % p == (a % p) * (b % p)
, quindi possiamo prendere il modulo quando vogliamo in sostanza.esp >>= 1
: passiamo al bit successivo dell’esponente.base = (base * base) % mod
: il bit successivo corrisponde al quadrato della base attuale. Per esempio, il quinto bit corrisponde a:
Tutto qua! Il primo algoritmo è O(esp)
, mentre il secondo è
O(log(esp))
, quindi decisamente più efficiente.