Aritmetica modulare e gestione circolare degli interi ● ● Scrivere un programma che abbia come parametri della riga di comando: – un numero intero k – una sequenza di stringhe e restituisca la sequenza di stringhe crittografate secondo il metodo di Giulio Cesare (ogni carattere viene sostituito con il k-esimo carattere successivo nell'ordinamento alfabetico; l'alfabeto è utilizzato circolarmente ovvero dopo la 'z' si ricomincia dalla 'a') Aritmetica modulare ● ● Ad es. gestire circolarmente l'indice di un array consente di rientrare nell'array dalla parte opposta (se si supera n-1 si passa alla posizione 0 e viceversa). Nella crittografia richiesta, se si oltrepassa la 'z' si rientra dalla lettera 'a' e viceversa 'la' =>'vk' 'vispa' =>'fsczk' 'teresa' =>'dobock' 'avea' =>'kfok' Soluzione 1 void codif( unsigned char *s, unsigned char k){ } while (*s){ *s =*s + k; if (*s > 'z') *s = *s - 26; s++; Una prima soluzione consiste nel verificare se il } valore della variabile ha superato il massimo; in tal caso si sottrae 26 al valore ottenuto. Il fatto di utilizzare comunque delle rappresentazioni limitate nel numero di bit pone dei problemi ... char e unsigned char void codif( unsigned char *s, unsigned char k){ } while (*s){ *s =*s + k; if (*s > 'z') *s = *s - 26; s++; } char in C è un intero con segno a 8 bit (-128 .. +127) unsigned char è un intero senza segno a 8 bit (0..255) Se il nuovo carattere è oltre la 'z' ricomincia dalla 'a'. char e unsigned char ● ● char è un intero con segno a 8 bit (-128 .. +127) se *s + k supera 127 si ha l'overflow e il risultato è considerato negativo – ● es: 'z'+10 = 122 + 10 = -124 ('z' è codificata in ASCII con 122) Il test successivo (*s > 'z') non viene soddisfatto e il carattere risultante non sarà sicuramente quello che ci si aspetta – nell'esempio invece di 'j' (il decimo carattere circolarmente dopo la 'z') si otterrà un carattere diverso (il 132 del codice ASCII) char e unsigned char ● ● unsigned char è un intero senza segno a 8 bit (0.. 255) in tal caso il risultato della somma viene considerato sempre positivo e il test viene soddisfatto. – 'z' + 10 = 122 +10 = 132 – 132 > 'z'risulta vero e quindi il valore assegnato a *s sarà 132-26=106 che corrisponde al carattere 'j' Aritmetica modulare ● In generale gestire circolarmente gli interi significa realizzare una aritmetica modulare in cui ad ogni intero k corrisponde un intero compreso tra 0 e n-1 0 1 2 .. n-1 n n+1 ... 2n-1 2n .. 0 1 2 .. n-1 0 1 ... n-1 0 .. Aritmetica modulare ● Il problema si pone per i numeri negativi. Nella gestione circolare di un indice ad esempio, dovremo avere questa corrispondenza: 0 -1 -2 .. -(n-1) -n -(n+1) ... -(2n-1) -(2n) .. 0 n-1 n-2 .. 1 0 n-1 .. 1 0 .. In tal modo il resto ha sempre un valore positivo: n=q⋅d r n : dividendo q : quoziente (l'intero più grande che moltiplicato per d fornisce un risultato minore o uguale a n) r : resto della divisione (sempre positivo) Es. resto della divisione per 5 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 ... 5 6 7 8 9 10 11 12 13 14 ... 0 1 2 3 4 Operatore % ● L'operatore % di resto della divisione se applicato a dividendi negativi fornisce il resto negativo. Es. -7 % 5 = -2 ● Per ottenere il risultato desiderato si può sommare al resto così ottenuto (compreso tra (d-1) e 0 ) il divisore (in modo da avere un valore tra 1 e d) ed effettuare di nuovo il resto. Resto della divisione per 5 n -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 n%5 -1 0 -4 -3 -2 -1 0 -4 -3 -2 -1 0 1 n%5+5 4 5 1 2 3 4 5 1 2 3 4 5 6 (n%5+5)%5 4 0 1 2 3 4 0 1 2 3 4 0 1 Resto della divisione per 5 n -1 0 1 2 3 4 5 6 7 8 9 10 11 n%5 -1 0 1 2 3 4 0 1 2 3 4 0 1 n%5+5 4 5 6 7 8 9 5 6 7 8 9 5 6 (n%5+5)%5 4 0 1 2 3 4 0 1 2 3 4 0 1 Aritmetica modulare ● Utilizzando l'operatore di modulo (resto della divisione) si può ottenere una soluzione più significativa che considera anche gli spostamenti k negativi Soluzione alternativa main(int argc, char *argv[]){ int i; char k; Nel main il valore di k può anche essere negativo k=atoi(argv[1]); } for (i=2; i < argc; i++){ printf("'%s' =>",argv[i]); codif(argv[i],k); printf("'%s'\n",argv[i]); } Soluzione alternativa void codif(char *s, char k){ while (*s){ if ('a' <= *s && *s <= 'z'){ char x=*s-'a'; Dopo aver calcolato la distanza da 'a' si somma k e x= (x+k) % 26; prendendo il resto modulo 26 si rientra in un valore 0..25 if (x<0) x+=26; A meno che k sia negativo *s = 'a'+x; per cui x potrebbe risultare } negativo anch'esso (ma inferiore in modulo a 26); s++; basta sommare 26 e si } torna a posto } Soluzione alternativa 1 void codif(char *s, char k){ while (*s){ if ('a' <= *s && *s <= 'z'){ char x=*s -'a'+k; Dopo aver calcolato la distanza da 'a' si somma k x=(x%26+26)%26; ottenendo un numero che può anche essere negativo *s = 'a'+x; } s++; x%26 sarà un numero compreso tra -25 e +25; sommando 26 si ottiene un numero } compreso tra 1 e 51 (quindi solo positivi); un ulteriore modulo restituisce il valore tra 0 e } 25. Ad es. se x=-1 (un carattere prima di 'a' e quindi 'z') si ottiene (-1 % 26 +26)%26 = (-1 +26)%26 = 25%26=25