Complessità degli algoritmi Complessità degli algoritmi • L’efficienza di un algoritmo si valuta in base all’utilizzo che l’algoritmo fa delle risorse del calcolatore: (Cap. 13) CPU Memoria tempo spazio • Algoritmi diversi, pur occupando lo stesso spazio, possono richiedere tempi diversi: • due algoritmi richiedono l’utilizzo di un array, ma uno richiede la scansione di tutto l’array, e impiega un tempo t1 , l’altro richiede la scansione solo di una parte dell’array, e impiega un tempo t2<t1. Complessità degli algoritmi • Si preferisce valutare l’efficienza in base al tempo impiegato per risolvere il problema. • Come si può misurare il tempo impiegato dall’algoritmo? Con un cronometro? • Ogni linguaggio ammette delle funzioni per calcolare il tempo: in alcuni si accede ad un “orologio” della macchina; spesso viene fornito un tempo calcolato in secondi o millisecondi e viene indicato il tempo trascorso a partire da una data fissa: in questo caso per calcolare il tempo si effettuano due successive chiamate della funzione tempo e si fa la differenza. Complessità degli algoritmi • Se vogliamo confrontare algoritmi che risolvono lo stesso problema misurando il tempo che impiegano, dovremo: • usare la stessa macchina • usare lo stesso linguaggio • usare gli stessi dati. • Si potrà costruire un array di dati abbastanza grande e tale vettore sarà l’unico ingresso per confrontare algoritmi che risolvono quel problema in prove eseguite sullo stesso calcolatore. Complessità degli algoritmi • È una buona misurazione? • Lo è solo in parte; non lo è in senso assoluto. Infatti il tempo misurato in tale modo dipende: • dal calcolatore usato • dal linguaggio usato • dai dati che sono elaborati in quel momento • Non ha perciò senso dire che “l’algoritmo di ricerca impiega 10 sec.”; il tempo dipende da fattori estranei all’algoritmo. Complessità degli algoritmi • Come possiamo stimare il tempo per l’algoritmo in modo che sia una misura assoluta, vale a dire, dipenda solo dall’algoritmo? • Osserviamo che il tempo di esecuzione è una funzione crescente della dimensione dei dati di ingresso. • Esempio. Sommare n numeri con n=10, 1000, 100000000; il tempo per calcolare la somma aumenta con n n → +∞ ⇒ T(n) → +∞ 1 Complessità degli algoritmi Complessità degli algoritmi • Non per tutti gli algoritmi è così: nell’algoritmo che calcola il massimo tra due numeri, in quello che scrive l’acronimo, nell’algoritmo che esegue delle prove sul troncamento della divisione tra interi, … i dati di ingresso sono una quantità fissa e non variano con la dimensione. • Nel calcolo della somma e del massimo su n dati, nell’algoritmo di ricerca, andiamo ad acquisire il valore di n e gli n valori. • Osserviamo che il numero dei dati di ingresso fa variare il numero di operazioni: n = 10 l’algoritmo esegue 10 somme n = 10000 l’algoritmo esegue 10000 somme • Si chiama complessità computazionale la funzione F(n) che calcola il numero di operazioni eseguite dall’algoritmo: n ∈ dimensione dei dati di ingresso F(n) numero di operazioni n → +∞ ⇒ F(n) → +∞ Complessità degli algoritmi Complessità degli algoritmi • Ha interesse sapere come la funzione F(n) “cresce” , vale a dire quale è il suo ordine di infinito. • Quali sono le operazioni che un algoritmo esegue? • Confrontiamo le due funzioni y = x e y = x2 osservando i valori che assumono per x = 5, 10, 100 x y=x y = x2 5 5 25 10 10 100 100 100 10000 intuiamo che y = x2 tende a +∞ più rapidamente di y = x; y=x2 ha un ordine di infinito maggiore di y=x. • • • • • operazioni elementari assegnazioni (accesso e assegnamento) confronti lettura/scrittura invocazione di metodi (passaggio parametri e gruppi di istruzioni all’interno del metodo) • strutture di controllo Complessità degli algoritmi Complessità degli algoritmi • Per trovare il tempo totale richiesto dall’algoritmo, dobbiamo sommare i tempi delle varie istruzioni. • Non è necessario, però, essere così precisi: possiamo fare delle semplificazioni. • Indicheremo con ta il tempo per effettuare una assegnazione e con tc il tempo per effettuare un confronto indipendentemente dalla semplicità o meno dell’espressione. • Invece di considerare assegnazioni e confronti si può calcolare il numero di accessi ad alcune variabili fondamentali nell’algoritmo: ad esempio nel confronto a[i] == b, c’è un accesso all’elemento i-esimo dell’array e a b. • Le operazioni fondamentali sono: confronti e assegnazioni 2 Complessità degli algoritmi • Assegnazione. 1) a = 25; 2) a = Math.sqrt(3*Math.sin(x)); • Queste due assegnazioni necessitano di due tempi diversi e sicuramente sarà: t1 < t2 perché l’espressione 2) richiede più calcoli dell’espressione 1). • Indichiamo pertanto con ta il tempo per una assegnazione generica (indipendentemente dall’espressione a destra del simbolo =). Complessità degli algoritmi • Struttura condizionale. se P allora <a1> altrimenti <a2> //finese dove <a1> e <a2> rappresentano gruppi di istruzioni • Il predicato P potrà essere: 1) a<b 2) (a<b) o (non S) e ((a==c) o (c!=h) • Certamente si avrà t1 < t2 ma consideriamo tp il tempo per valutare un predicato generico. Complessità degli algoritmi • Sequenza di assegnazioni. <a1> <a2> …. <ak> k costante il numero di istruzioni non dipende da n • Sommiamo i tempi: k ta ~ ta k non dipende da n • Se abbiamo due funzioni F1(n) F2(n) con lo stesso ordine di infinito, può però essere importante stimare anche il valore di k. Complessità degli algoritmi • Avremo tp + ta1 tp + ta2 • Se <a1> e <a2> non dipendono da n (sequenza di istruzioni) tp + ta1 ~ tp + ta2 ~ tp • abbiamo un numero costante di operazioni, come nell’algoritmo del max(a,b). Complessità degli algoritmi Complessità degli algoritmi • Quali sono le strutture che variano con n? • Quando eseguiamo una scansione di un array (lettura, stampa, somma, ricerca, …) consideriamo un numero n (n>0) di dati di ingresso. • Per fare la scansione di un array utilizziamo una struttura iterativa. • Struttura iterativa. • Consideriamo un ciclo con incremento fisso e passo 1: per i da 1 a n eseguire iterazione //indipendente da n //fineper • Indichiamo con tp il tempo per il predicato e con ti il tempo per l'iterazione (per ora indipendente da n): 3 Complessità degli algoritmi • Avremo: tp : eseguito n+1 volte (n volte con P vero, 1 volta con P falso) ti : eseguita n volte ⇒ (n+1)·tp + n · ti ~ n · c c = costante: “costo” dell'iterazione: (n+1)·tp + n·ti = n·tp + tp + n·ti ≤ n·tp + n·tp + n·ti = 2n·tp + n·ti = n·( 2·tp + ti ) = n · c infatti n>0 quindi n ≥ 1 Complessità degli algoritmi (n+1)·tp1 + ((n+1) · tp2 + n·ti ) · n = = (n+1)·tp1 + ((n+1) · tp2)·n + n2·ti ≤ ≤ 2n· tp1 + 2n·tp2·n + n2·ti ≤ ≤ n2 ( 2 tp1+ 2 tp2 + ti) = n2 · c (avendo considerato: n+1 ≤ 2n e 2n ≤ 2n2 , essendo n naturale positivo) c= costante : “costo” dell'iterazione Complessità degli algoritmi • Cosa cambia se anche l’iterazione dipende da n? • Supponiamo che l’iterazione sia un ciclo dello stesso tipo, che viene eseguito n volte: 1. per i da 1 a n eseguire 2. per k da 1 a n eseguire iterazione //fineper //fineper Complessità degli algoritmi • Consideriamo delle funzioni di riferimento e calcoliamo la complessità degli algoritmi confrontandola con queste funzioni. y = log x y=x y = x log x y = x2 y=2x y=ex y = x2 Complessità degli algoritmi e y=2x si incontrano per x=2 e x=4 Complessità degli algoritmi • Notazione O (o-grande), Ω, Θ. (par. 13.3) • Ci interessa sapere come cresce F(n) al crescere di n, sempre nell’ipotesi che F(n) → +∞ quando n → +∞ • Sia f(n) la funzione di complessità che cerchiamo. Si dice che f(n) è O(g(n)) se ∃ c, n0 > 0 | ∀ n ≥ n0 f(n) ≤ c · g(n) • Come ordine di infinito f “non cresce più di” g. 4 Complessità degli algoritmi Complessità degli algoritmi • Se f(n) = O(g(n)) significa che g(n) è una limitazione superiore per f(n): il calcolo esatto di f(n) è troppo complicato e ci limitiamo a stimarne una limitazione superiore. • Esempio. n2 + n è O(n2) infatti: n2 + n ≤ n2 + n2 = 2· n2 • Sia f(n) la funzione di complessità che cerchiamo. Si dice che f(n) è Ω(g(n)) se ∃ c, n0 > 0 | ∀ n ≥ n0 c · g(n) ≤ f(n) • Come ordine di infinito f “cresce almeno quanto” g; g(n) è una limitazione inferiore per f(n). • Esempio. n2 + n è Ω(n) infatti: 2n = n + n ≤ n2 + n Complessità degli algoritmi • Sia f(n) la funzione di complessità che cerchiamo. Si dice che f(n) è Θ(g(n)) se ∃ c1, c2, n0 > 0 | ∀ n ≥ n0 c1 · g(n) ≤ f(n) ≤ c2 · g(n) • Come ordine di infinito f “cresce quanto” g. Esempio. n2 , 1000 n2, 1/100 n2 sono tutte Θ(n2) varia solo la costante moltiplicativa. Classi di complessità • Quando scriviamo un algoritmo, vogliamo stimare la sua complessità cercando di individuare la funzione g(n) che approssima f(n). In tale modo gli algoritmi sono suddivisi in classi di complessità: costanti k (non dipendono da n) logaritmo log n , log2 n lineari n n log n polinomiali nk k = 2, 3, … esponenziali n! , an , nn a ≠ 0,1 Casi di prova per stimare la complessità Casi di prova per stimare la complessità • Quando si scrive un algoritmo si deve sempre dare una stima della limitazione superiore; è opportuno dare una stima della limitazione inferiore, sarebbe importante stimare un comportamento medio tra le due stime (difficile). • O(g) limitazione superiore: sono richieste al più g(n) operazioni • Ω(g) limitazione inferiore: sono richieste almeno g(n) operazioni. • Casi di prova. • Caso peggiore: i dati sui quali l’algoritmo richiede il massimo numero di operazioni • Caso favorevole: i dati sui quali l’algoritmo richiede il minor numero di operazioni • Caso medio: i dati che richiedono un numero medio di operazioni. • Nel calcolo della complessità non si considerano i cicli per acquisire i dati: sono uguali per tutti gli algoritmi e sono Θ(n). 5 Complessità dell’algoritmo di ricerca lineare • Caso I: elementi distinti. i←0 trovato ← falso mentre i ≠ n e non trovato eseguire i ← i+1 se a[i] == b allora trovato ← vero //fine //fine se trovato allora stampa “trovato al posto “ i altrimenti stampa “ elemento non presente” //fine Complessità dell’algoritmo di ricerca lineare • Caso peggiore. Si ha quando il ciclo viene eseguito fino in fondo. 1) l’elemento non c’è: predicato i←i+1 • Caso favorevole. Si ha quando a[1] = b: primo elemento dell’array: 2ta + tp + ta + tc + ta + tp + tc + tstampa inizio V i←i+1 a[i] == b trovato F se risultato la funzione non dipende da n, quindi è costante: Ω(1) è il caso con il minimo numero di operazioni. Complessità dell’algoritmo di ricerca lineare 2) l’elemento c’è ed è l’ultimo: a[n] = b 2ta + (n+1) tp + nta + ntc + ta + tc + tstampa 2ta + (n+1) tp + nta + ntc + tc + tstampa inizio Complessità dell’algoritmo di ricerca lineare a[i] == b se risultato la funzione è del tipo c·n, quindi O(n). Complessità dell’algoritmo di ricerca lineare • Caso II: elementi ripetuti. k←0 per i da 1 a n eseguire se a[i] == b allora k ← k+1 posiz[k] ← i //finescelta //fineciclo se k==0 allora stampa “elemento non presente” altrimenti stampare i primi k valori dell’array posiz: posiz[1], …, posiz[k] inizio predicato i←i+1 a[i] == b trovato se risultato la funzione è del tipo c·n, quindi O(n). • Il massimo numero di operazioni è dato da una funzione lineare. Complessità dell’algoritmo di ricerca lineare • La struttura iterativa for (“per”) viene eseguita sempre completamente. • Caso peggiore. Gli elementi sono tutti uguali a b: ogni volta si assegna un valore a posiz ta + (n+1)tp + ntc + 2nta + tc + tstampa ⇒ O(n) • Caso favorevole. L’elemento non c’è: il numero di assegnazioni è 0 ta + (n+1)tp + ntc + tc + tstampa ⇒ Ω(n) Pertanto la complessità è: Θ(n) 6 Complessità dell’algoritmo di ricerca lineare Complessità dell’algoritmo di ricerca lineare • Caso III: dati ordinati. • Il primo confronto a[1] <= b e b <= a[n] viene eseguito per verificare se è oppure no possibile eseguire la ricerca: nel caso in cui questo predicato fosse falso, non si esegue l’algoritmo. • Per l’algoritmo di ricerca lineare la complessità è: Ω(1) nel caso favorevole (primo elemento) ed è O(n) nel caso peggiore (ultimo o mancante). • Esercizio. Dimostrare l’affermazione precedente. • Questo non viene considerato come caso favorevole, perché di fatto l’algoritmo non inizia nemmeno. • Per la ricerca binaria si ha: Complessità dell’algoritmo di ricerca binaria Complessità dell’algoritmo di ricerca binaria • Caso favorevole. • L’elemento viene trovato con un solo confronto; ma non è il primo elemento, è quello centrale: b = a[(1+n)/2] • Caso peggiore. • Si esegue l’iterazione il massimo numero di volte quando: l’elemento o è l’ultimo “guardato” oppure non è presente. • Quante operazioni ci sono nel ciclo? ta + tc + tc + ta numero costante 3ta + tp + ta + tc + ta + tp + tc + tstampe a[im]==b trovato numero costante di operazioni: se Ω(1) a[im]==b a[im]>b is o id • Quante iterazioni? Facciamo una stima del numero di iterazioni eseguite valutando quanti elementi restano ad ogni divisione dell’array (gli elementi sono in numero finito). Complessità dell’algoritmo di ricerca binaria Complessità dell’algoritmo di ricerca binaria • Supponiamo che n = 2k 1a iterazione restano 2k-1 elementi 2a iterazione restano 2k-2 elementi 3a iterazione restano 2k-3 elementi ….. k-esima iterazione restano 2k-k = 1 elementi: is=id (k+1)-esima iterazione: se l’elemento è presente trovato diventa vero, altrimenti trovato è falso e is>id: il ciclo termina: O(k) con k = log2 n : O(log2 n) • Si può dimostrare anche per valori di n qualunque, poiché si ha 2k-1≤ n ≤ 2k e che la complessità del caso peggiore è O(log2 n) 7 Casi di prova Casi di prova Casi di prova • Lo schema del programma sarà perciò del tipo: //acquisire e stampare l'array //acquisire la quantità dei casi di prova // numprove for(int k = 1; k <= numprove; k++){ //acquisire l'elemento b da cercare //chiamare il metodo di ricerca //stampare i risultati System.out.println("prova numero " + k + " elemento cercato = " + b); } • Quando si vuole testare un algoritmo si devono costruire vari casi di prova. Invece di eseguire il programma più volte si può costruire un file di dati contenente tutti i casi che si vogliono provare. • Esempio. Supponiamo di voler provare un algoritmo di ricerca: si acquisiscono i valori di un array (unico) e i casi di prova saranno costituiti da diversi valori di b (b esterno all’array, presente, presente più volte, ecc.) Casi di prova • Nel file dei dati si avrà pertanto la seguente organizzazione sequenziale: n e gli n elementi per l’array num (numero di prove ) num elementi da cercare • Il programma viene eseguito una sola volta e produce i vari risultati dell’esecuzione dell’algoritmo con i diversi valori da cercare. Argomenti sulla riga di comando Argomenti sulla riga di comando • Quando si esegue un programma Java, è possibile fornire dei parametri dopo il nome della classe che contiene il metodo main. • Tali parametri vengono letti dall’interprete Java e trasformati in un array di stringhe che costituisce il parametro del metodo main. • Supponiamo di inserire due valori per il programma di nome Prova java Prova mio 50 8 Argomenti sulla riga di comando Argomenti sulla riga di comando public class Prova { public static void main (String[] arg) { System.out.println(arg.length); System.out.println(arg[0]); System.out.println(arg[1]); . . . } } • La lunghezza dell’array è 2: il primo argomento è la stringa “mio”, il secondo argomento il valore 50. • Questi valori inseriti possono essere dei dati di ingresso da elaborare, spesso si inserisce il nome di uno o più file che si vogliono collegare al programma oltre ai file standard System.in e System.out. • Il parametro del main è un array di tipo String, perciò se dobbiamo considerare valori numerici dobbiamo poi trasformarli. Argomenti sulla riga di comando • Finora non abbiamo inserito argomenti: cosa veniva associato all’array di stringhe arg? • Se non vengono forniti parametri sulla riga di comando, al metodo main viene passato un array di lunghezza zero. Scelte multiple: switch • Si poteva pensare che arg = null; invece il parametro fornito al metodo main non ha mai il valore null. • Esercizio. Pensare ad un modo per verificare quanto detto. Scelte multiple: switch • L’enunciato switch si usa al posto di una situazione in cui ci siano scelte del tipo “else if“ annidate. (Argomenti avanzati 5.2) • Esempio. int x, y; if (x == 1) y = 1; else if (x == 2) y = 4; else if (x == 4) y = 16; Scelte multiple: switch • Sintassi. //k deve essere intero //oppure carattere //acquisire o assegnare un valore a k switch(k){ case costante1: istruzione; case costante2: istruzione; case costante3: istruzione; } int k; 9 Scelte multiple: switch Scelte multiple: switch • Semantica. • Il valore di k viene cercato tra i valori presenti nella case e se viene trovato si esegue l’istruzione indicata e tutte le successive. • Se si vuole che le istruzioni siano in alternativa, come avviene con l’annidamento delle if, si deve utilizzare la parola chiave break. switch(k){ case costante1: istruzione; break; case costante2: istruzione; break; case costante3: istruzione; } Scelte multiple: switch Scelte multiple: switch switch(k){ case costante1: istruzione; break; case costante2: istruzione; break; case costante3: istruzione; break; default: System.out.println("il "+ "valore di k non e' presente"); } • Se non si usa break, l’ordine delle case fa variare le operazioni da eseguire. • Se il valore di k non è presente, non vi è alcuna segnalazione. • Per gestire meglio il caso “valore di k non presente”, si può introdurre un “default”. • Esempio. switch (x){ case 1: y = 1; case 2: y = 4; case 4: y = 16; } //se x = 1 y = 16 switch (x){ case 4: y = 16; case 2: y = 4; case 1: y = 1; } //se x = 1 y = 1 Scelte multiple: switch • Questo enunciato è utile quando si devono scegliere metodi diversi da eseguire in alternativa, come un menu che propone delle scelte: la lettura del menu è sicuramente più chiara che non l’elenco delle varie “else if”. Ciclo do • Se la variabile non è intera (o carattere) o se i valori non sono delle costanti non è possibile utilizzare l’enunciato switch. 10 Ciclo do Ciclo do • Capita a volte di dover eseguire il corpo di un ciclo almeno una volta, per poi ripeterne l’esecuzione se è verificata una particolare condizione. (Argomenti avanzati 6.1) • Sintassi. • Esempio: leggiamo un valore in ingresso e vogliamo che esso sia positivo: se il valore non è positivo vogliamo poter ripetere la lettura. • Semantica. L’iterazione viene eseguita la prima volta; viene valutata la condizione: se è vera si esegue nuovamente l’iterazione, se è falsa il ciclo termina. Ciclo do Ciclo do • Equivale a scrivere: iterazione; while(condizione){ iterazione; } • oppure boolean continua =true; while(continua){ iterazione; if(!condizione) continua=false; } do { //iterazione } while(condizione); • Problema. Calcolare la somma 1 +1./2 + 1./3 + 1./4 + … + 1./n + … fino a quando 1./n > t con t valore stabilito (ad esempio t = 10-9). • Esercizio. Scrivere l’algoritmo usando sia il ciclo while che il ciclo do. Il problema dell’ordinamento Il problema dell’ordinamento • Dati n elementi sui quali si possa considerare un relazione d’ordine totale (ogni coppia di elementi è confrontabile) costruire la permutazione ordinata, vale a dire trovare una disposizione degli elementi in modo tale che si abbia: a1 < a2 < a3 < …. < an • Analogamente possiamo considerare elementi ordinati in ordine decrescente. gli 11 Il problema dell’ordinamento • Vedremo metodi diversi e ne calcoleremo la complessità. • I metodi di ordinamento saranno: - ordinamento scansione (o selezione diretta) - bubblesort - ordinamento per inserimento - ordinamenti ricorsivi - quicksort - mergesort • La complessità varia da O(n2) a O(n··log2 n) Ordinamento per scansione mentre ci sono elementi da ordinare eseguire mettere all’i-esimo posto il minimo degli elementi {ai, ai+1, …., an} //finementre • Per trovare il minimo abbiamo due strategie: • I. considerare gli elementi ai e ak con k = i+1, …, n e scambiarli se non sono nell’ordine Ordinamento per scansione • Non possiamo fare subito l’assegnazione x=y altrimenti x e y avrebbero lo stesso valore. Dobbiamo utilizzare una variabile di supporto, s dello stesso tipo di x e y, per depositare temporaneamente uno dei due valori: 1) s=x; 2) x=y; 3) y=s; x 7 5 s • Uno scambio si fa con 3 assegnazioni y 5 7 7 Ordinamento per scansione (selezione) • Analisi. (par. 13.1) • Se vogliamo mettere in ordine gli elementi osserviamo che: a1 è il minimo tra gli elementi {a1, a2, … an}, a2 è il minimo tra gli elementi {a2, a3, … an}, ecc. , an-1 è il minimo tra gli elementi {an-1, an}, e an è al suo posto. • Progetto. • Avremo una struttura iterativa per “mettere a posto” a1, a2,…, an-1 cercando il minimo. Ordinamento per scansione • II. calcolare il valore e la posizione del minimo ed eseguire un solo scambio tra ai e il minimo trovato. • Come si effettua lo scambio? • Abbiamo due variabili x e y e vogliamo scambiare il loro contenuto: prima dopo 7 5 x y 5 x 7 y Ordinamento per scansione //ordinamento per scansione con scambio per i da 1 a n-1 eseguire //mettere in ai il minimo tra ai , ai+1 , …, an per k da i+1 a n eseguire se a[i] > a[k] allora //scambiare ai con ak s ← a[i] a[i] ← a[k] a[k] ← s //finese //fineper //fineper 12 Ordinamento per scansione • Calcoliamo la complessità dell’algoritmo per scansione. • L’operazione fondamentale eseguita nella struttura iterativa è il confronto: questo viene eseguito sempre, mentre lo scambio viene eseguito solo nel caso vero. • Quanti confronti vengono eseguiti? • Il ciclo esterno varia da 1 a n-1, il ciclo interno varia da i a n: i non è fisso e quindi il calcolo esatto è un po’ più laborioso. Ordinamento per scansione n° iterazione elementi n° confronti i=1 (a1, a2) (a1, a3) … (a1, an) n-1 i=2 (a2, a3) … (a2, an) n-2 …… ……. i=n-1 (an-1, an) 1 Sommiamo i confronti: 1 + 2 + 3 + …. + n-2 + n-1= n·(n-1)/2 Ordinamento per scansione Ordinamento per scansione • Un modo facile per ricordare la somma (intero: n o n-1 è pari) è: • Caso favorevole. Si fanno tutti i confronti e nessuno scambio; dati: vettore ordinato: n(n-1)/2 confronti + 0 assegnazioni • Quindi Ω(n2/2) (il primo + l’ultimo) × (metà del numero di elementi) si può dimostrare la formula per induzione. • Caso peggiore. Si fanno tutti i confronti e tutti gli scambi; dati: vettore ordinato in ordine inverso: n(n-1)/2 confronti + 3n(n-1)/2 assegnazioni Quindi O(n2 / 2). Ordinamento per scansione //ordinamento per scansione con minimo per i da 1 a n-1 eseguire //mettere in ai il minimo tra ai , ai+1 , …, an imin ← i per k da i+1 a n eseguire se a[imin] > a[k] allora imin ← k //finese //fineper s ← a[i] //scambiare ai con aimin a[i] ← a[imin] a[imin] ← s //fineper • Si ha pertanto: • O(n2 /2) come limitazione superiore • Ω(n2/2) come limitazione inferiore Θ(n2 / 2) (anche Θ(n2) ) Ordinamento per scansione • Caso peggiore. confronti: n (n-1) / 2 assegnazioni: sono (al più) 4(n-1) (ciclo esterno) + n(n-1)/2 (ciclo interno) • Caso favorevole. confronti: n (n-1) / 2 assegnazioni: 4(n-1) (ciclo esterno) 13 Ordinamento per scansione • Confrontiamo le due versioni: • il numero di confronti è sempre: n(n-1) / 2 • varia il numero di assegnazioni: • nel caso favorevole si ha: 0 (scambio) 4(n-1) (minimo) • nel caso peggiore si ha: 3(n-1)n /2 (scambio) (n-1)n/2 + 4(n-1) (minimo) • Verificare che per n>4 3(n-1)n /2 > (n-1)n / 2 + 4(n-1) Ordinamento bubblesort • Si eseguono confronti tra elementi successivi: (a1, a2) (a2, a3) (a3, a4) … (an-1, an) e se gli elementi non sono nell’ordine allora si scambiano. Con questa tecnica la componente con valore più grande va in fondo (al posto di an). Si ricomincia dell’inizio e si “mette a posto” an-1, poi an-2, … , infine a2. • Si può anche partire dal fondo iniziando il confronto dalla coppia (an-1, an) e andare indietro: in tale modo la prima componente che si “mette a posto” è a1 (in testa il minimo). Ordinamento bubblesort • Progetto. per i da 1 a n-1 eseguire per k da 1 a n-i eseguire se a[k] > a[k+1] allora //scambiare ak con ak+1 s ← a[k] a[k] ← a[k+1] a[k+1] ← s //finese //fineper //fineper Ordinamento bubblesort • Quante assegnazioni? • Caso favorevole. 0 assegnazioni (già ordinato) • Caso peggiore. 3·n·(n-1)/2 (ordine inverso) • Pertanto anche il bubblesort è Θ(n2 / 2). Ordinamento bubblesort • Quanti confronti vengono effettuati? n° iterazione i=1 i=2 i=n-1 elementi n° confronti (a1, a2) (a2, a3) … (an-1, an) n-1 (a1, a2) … (an-2, an-1) n-2 …… ……. (a1, a2) 1 Sommiamo i confronti: sempre 1 + 2 + 3 + …. + n-2 + n-1= n·(n-1)/2 O(n2 /2) Confronto tra i vari metodi di ordinamento • Consideriamo il seguente array e vediamo come si muovono gli elementi (alla prima iterazione). a = ( 40, 20, 1, 7, 10, 0) n=6 1)lineare con scambio 40 20 1 7 10 0 scambio 40, 20 20 40 1 7 10 0 scambio 20, 1 1 40 20 7 10 0 scambio 1, 0 0 40 20 7 10 1 14 Confronto tra i vari metodi di ordinamento 2) lineare con minimo 40 20 1 7 10 0 imin 0 1 2 3 6 scambio 40, 0 20 1 7 10 40 Confronto tra i vari metodi di ordinamento 23) bubblesort 40 20 1 7 20 40 1 7 20 1 40 7 20 1 7 40 20 1 7 10 20 1 7 10 10 10 10 10 40 0 0 0 0 0 0 40 scambio 40, 20 scambio 40, 1 scambio 40, 7 scambio 40, 10 scambio 40, 0 Confronto tra i vari metodi di ordinamento Confronto tra i vari metodi di ordinamento • Se nel bubblesort si mette il minimo in testa si ottiene: 0 40 20 1 7 10 • Esercizio1. Costruire una variante di bubblesort che tenga conto che se non si fanno scambi l’array è ordinato. • Osserviamo che, dal momento che nel bubblesort si confronta ak con ak+1, se non ci sono scambi allora significa che l’array è ordinato. • Si può sfruttare questa proprietà per scrivere una versione più efficiente. Confronto tra i vari metodi di ordinamento • Esercizio2. • Costruire una variante che tenga conto della posizione dell’ultimo scambio. • Esempio. a = ( 3, 1, 2, 5, 7, 8, 9, 12, 34, 35) n=10 dalla quarta posizione in poi non si fanno scambi. Suggerimento. Pensare agli estremi dell’indice come “inizio” e “fine” della parte di array da esaminare; memorizzare in una variabile “sc” la posizione dell’ultimo scambio (fine cambierà il suo valore tramite sc). Il ciclo terminerà con inizio = fine Suggerimento. Utilizzare una variabile logica “ordinato” da inizializzare a falso e da inserire nel predicato del ciclo esterno (se non ci sono scambi ordinato deve essere vero). Confronto tra i vari metodi di ordinamento • Per entrambe queste varianti la complessità nel caso favorevole è inferiore a quella del caso senza la variante: infatti, se si fornisce un array già ordinato, dopo la prima iterazione il ciclo termina: ordinato=vero, oppure inizio=fine. • Pertanto il caso favorevole è Ω(n). • Anche nell’ordinamento per scansione è possibile eseguire tali varianti? No. 15 Esercizio • Si calcoli la complessità dell’algoritmo seguente, considerando i casi a>b, a=b, a<b; specificando il numero di assegnazioni e di confronti nei tre casi. • Si supponga n>0. • Quale sarebbe il valore di a e b se si stampassero a e b alla fine della struttura iterativa “mentre”? Esercizio Algoritmo definizione variabili a, b, n, i, k intero continua logico acquisire valori per a, b, n continua ← vero i←0 mentre i ≠ n e continua eseguire i ← i+1 se a < b allora a ← a-1 Esercizio altrimenti se a == b allora per k da 1 a n eseguire a ← a+1 b ← b+1 //fineper altrimenti continua ← falso //finese //finese //finementre Soluzione esercizio Soluzione esercizio Soluzione esercizio • Caso n = 0 . i = 0, quindi il predicato “mentre i ≠ 0 …” è falso: il ciclo non viene eseguito. • Caso n > 0 . • Caso a < b . Viene eseguita l’istruzione a ← a-1, quindi è ancora a < b; il ciclo termina quando i = n; il numero di operazioni è: 2ta + (n+1) tp + nta + ntc + nta ⇒ c·n • Caso n < 0 . se a < b il ciclo non termina: a diminuisce e quindi rimane minore di b se a = b il ciclo non termina: entrambi crescono di 1 se a > b il ciclo termina: continua diventa falso. continua←V i←0 i←i+1 a<b a←a-1 b non varia, a diventa a-n 16 Soluzione esercizio • Caso a > b . Viene eseguita l’istruzione continua ← falso, quindi il ciclo termina; il numero di operazioni è: 2ta + tp + ta + tc + tc + ta + tp ⇒ costante continua←V i←i+1 a<b a=b continua← F i← 0 b non varia, a non varia Soluzione esercizio • Caso a = b . Vengono eseguite le istruzioni a←a+1 e b←b+1; il ciclo interno viene eseguito n volte e il ciclo esterno termina quando i = n; il numero di operazioni è: 2ta + (n+1) tp + nta + ntc + n tc + n(n+1) tp+ continua←V i←i+1 a<b a=b i←0 + 2 n2ta ⇒ c·n2 a← a+1 b← b+1 a diventa a + n2 , b diventa b + n2 Rappresentazione dell’informazione numerica • Esistono vari tipi di numerazione. Rappresentazione dell’informazione numerica • Primitiva: sequenza di simboli tutti uguali; numerazione nei dadi, carte da gioco, … Rappresentazione dell’informazione numerica Rappresentazione dell’informazione numerica • Posizionale: le cifre che compongono la sequenza di simboli che rappresentano il numero hanno un valore che varia con la posizione, si esegue poi la somma; è la numerazione attuale. • Nella rappresentazione posizionale il numero viene scritto con una notazione del tipo seguente: N = cn cn-1 … c0 . c-1 c-2 … c-m • Additiva: ogni simbolo ha un valore fisso; si esegue una somma dei valori per determinare il numero; numerazione dei romani, greci, egiziani,… • Il valore di ogni cifra è: cifra × bp dove b è la base del sistema di numerazione (con b ≠ 0,1) e p è la posizione della cifra. • Esempio. 3 300 0.03 unità centinaia centesimi 17 Rappresentazione dell’informazione numerica • Il valore del numero viene dalla somma: N = cn bn + cn-1 bn-1 + … + c0 b0 + + c-1 b-1 + …. + c-m b-m Rappresentazione dell’informazione numerica • Il più piccolo numero naturale rappresentabile con n cifre ha tutte le cifre uguali a 0: 0 bn-1 + 0 bn-2 + … + 0 b0 = 0 • con ck = 0, 1, 2, …, b-1 • N si ottiene quindi sommando i valori ck bk. • Formula di rappresentazione dei numeri (per i numeri con infinite cifre dopo la virgola, si ha una serie). (Appendice H) • Il più grande numero naturale rappresentabile con n cifre ha tutte le cifre uguali a b-1: Rappresentazione dell’informazione numerica Rappresentazione dell’informazione numerica Si possono considerare varie basi. b=10 sistema decimale ck = 0, 1, 2,.., 9 b=2 sistema binario ck = 0, 1 b=8 sistema ottale ck = 0, 1, 2,.., 7 b=16 sistema esadecimale ck = 0, 1, 2,.., 9, A, B, C, D, E, F • b=60 sistema sessagesimale: si usa per misurare il tempo, non si usano 60 simboli diversi, ma simboli sono scritti in decimale con due cifre: 00, 01, 02, … 57, 58, 59. • • • • • Rappresentazione dell’informazione numerica b=2 b=10 9 (10) (11) (12) (13) (14) (15) b=8 b=16 9 A B C D E F 4bit 1001 1010 1011 1100 1101 1110 1111 • Per scrivere in base 2 i numeri della base 16 = 24 occorrono 4 bit. (b-1) bn-1 + (b-1) bn-2 + … + (b-1) b0 = bn-1 • Cifre delle basi 2, 10, 8 , 16 b=2 b=10 b=8 b=16 0 0 0 0 1 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 4 bit 0000 0001 0010 0011 0100 0101 0110 0111 1000 Cambi di base • Passaggio da base b a base 10. • Il numero è scritto con le cifre della base b: si rappresentano le cifre nella base 10 e si fanno i calcoli in base 10. • Esempio. b=2 a) 1102 = 1× ×22 + 1× ×21 + 0× ×20 = 4 + 2 = 610 1 0 b) 112 = 1× ×2 + 1× ×2 = 2 + 1 = 310 c) 11002 = 1× ×23 + 1× ×22 + 0× ×21 + 0× ×20 = = 8 + 4 = 1210 18 Cambi di base • Le regole per la base 10 valgono anche nelle altre basi: - spostare la virgola a destra o aggiungere uno zero destra vuole dire “moltiplicare” per la base: b) → a) e a) → c) - spostare la virgola a sinistra o togliere uno zero sinistra vuole dire “dividere” per la base: a) → b) e c) → a) . Cambi di base • Esempio. b=8 20.48 = 2 ×81 + 0 ×80 + 4 ×8-1 = = 16 + 0 + 4/8 = 16.510 • Esempio. b=16 AF.C16 = 10 ×161 + 15 ×160 + 12 ×16-1 = = 160 + 15 + 12/16 = = 175 + 3/4 = 175.7510 Cambi di base • Esempio. b=7 1527 = 1 ×72 + 5 ×71 + 2 ×70 = 49 + 35+ 2 = 8610 257 = 2 ×71 + 5 ×70 = 14 + 5 = 1910 • Esempio. b=5 4445 = 4 ×52 + 4 ×51 + 4 ×50 = = 4 ×25 + 20 + 4 = 100 + 24 = 12410 Cambi di base • Esempio. b=16 30B.A16 = 3 ×162 + 0× ×161 + 11× ×160 + 10 ×16-1 = 3 ×256 + 0 + 11 + 10/16 = = 768 + 11 + 0.625 = = 779.62510 • Regola. Si scrivono le cifre del numero nella base 10 (che per b<10 coincidono con quelle della base 10) e anche la base b (in base 10) e si fanno i calcoli in base 10. Cambi di base Cambi di base • Passaggio da base 10 a base b. • Se sapessimo fare con disinvoltura i conti nella base b potremmo scrivere le cifre in base b ed eseguire i conti in base b in maniera analoga. • Per poter fare sempre i conti in base 10 usiamo la seguente regola: consideriamo il numero suddiviso nella sua parte intera e parte frazionaria PI . PF = cn cn-1 … c0 . c-1 c-2 … c-m • PI : si divide PI e i successivi quozienti per la base e si prendono i resti • PF : si moltiplica PF e le successive parti frazionarie per la base si prendono le parti intere. 19 Cambi di base Cambi di base • Sia N10 = c3 c2 c1 c0 . c-1 c-2 c-3 in base b; noi volgiamo trovare le cifre ck. PI = c3 b3 + c2 b2 + c1 b1 + c0 b0 = = b (c3 b2 + c2 b1 + c1 b0 ) + c0 (b0 =1) dividendo per b si ottiene: c3 b2 + c2 b1 + c1 quoziente e c0 è il resto Si prosegue con il quoziente trovato: c3 b2 + c2 b1 + c1 = b (c3 b1 + c2 ) + c1 dividendo per b c1 è il resto c3 b + c2 = b (c3) + c2 c2 è il resto c3 = b·0 + c3 c3 è il resto (quoziente = 0 perché c3 < b) • Abbiamo estratto nell’ordine c0, c1, c2, c3 che andranno scritti: c3 c2 c1 c0 . Cambi di base PF = c-1 b-1 + c-2 b-2 + c-3 b-3 = (finita) = b-1 (c-1 + c-2 b-1 + c-3 b-2) moltiplicando per b si ottiene: b·b-1 (c-1 + c-2 b-1 + c-3 b-2) = = c-1 + c-2 b-1 + c-3 b-2 c-1 è la parte intera c-2 b-1 + c-3 b-2 la parte frazionaria Cambi di base Si prosegue con la parte frazionaria trovata: c-2 b-1 + c-3 b-2 = b-1 (c-2 + c-3 b-1) c-2 è la parte intera moltiplicando per b b· c-3 b-1 = c-3 c-3 è la parte intera • Abbiamo estratto nell’ordine c-1, c-2, c-3 che andranno scritti: . c-1 c-2 c-3 Cambi di base • Esempio. 22710 = ? 2 Cambi di base • Esempio. 111000112 13610 = ? 2 resti 227 2 113 56 28 14 7 3 1 0 1 1 0 0 0 1 1 1 100010002 resti Verifica: 27 + 26 + 25 + 21 + 20 = = 128 + 64 + 32 + 2 + 1 = = 227 136 2 68 34 17 8 4 2 1 0 0 0 0 1 0 0 0 1 Verifica: 27 + 23 = = 128 + 8 = 136 20 Cambi di base • Esempio. 0.7510 = ? 2 0.112 0.75 × 2 parte intera 1.5 : 0.5 × 2 1 1.0 1 Verifica: 1 /2 + 1 /4 = 3 / 4 = = 0.75 la parte frazionaria è 0 Cambi di base • Esempio. 0.410 = ? 2 0. 01102 0.4 × 2 parte intera 0.8 : 0.8 × 2 0 Verifica: 1 / 4 + 1 /8 + 1 / 64 + … = 1.6 : 0.6 × 2 1 =? 1.2 : 0.2 × 2 1 il numero è approssimato 0.4 : 0.4 × 2 0 0.8 …. le parti frazionarie si ripetono: c’è un periodo: 0110 Cambi di base • Esempio. 0.110 = ? 2 0.1 × 2 parte intera 0.2 : 0.2 × 2 0 0.4 : 0.4 × 2 0 0.8 : 0.8 × 2 0 1.6 : 0.6 × 2 1 1.2 : 0.2 × 2 1 0.4 …. periodo è 0011 0. 0 00112 0.1 è periodico in base 2 Numeri frazionari periodici Numeri frazionari periodici Numeri frazionari periodici • Abbiamo visto che 0.110 = 0.0 00112 • Consideriamo un numero razionale: p/q con p e q naturali e primi fra loro e q≠0 Eseguendo la divisione p:q=… si può ottenere una rappresentazione finita oppure infinita e periodica della parte frazionaria. a) Se i fattori primi di q sono solo quelli della base, allora la rappresentazione è finita. b) Se tra i fattori primi di q ci sono fattori diversi dai fattori della base, allora la rappresentazione è infinita e periodica. • Vediamo per la base b = 10 = 2··5 • Caso a) q = 2k · 5h ; sia n = max(h,k); allora si può scrivere p m = q 10n con m naturale m = cs cs-1 …. c1 c0 21 Numeri frazionari periodici Numeri frazionari periodici • Spostando la virgola di n posti verso sinistra a partire da c0 si trova la rappresentazione finita, perché finito è il numero di cifre di m. • Esempio. • Caso b) q = ak · bh con a e b anche diversi da 2 e 5; ne segue che non si potrà scrivere q come potenza di 10. Quando si esegue la divisione: p = q k1 + k2 0 ≤ k2 < q se le cifre di p sono terminate si “abbassa lo 0” e si prosegue con la divisione. Il resto k2, variando in un intervallo limitato, ad un certo punto si ripete: si ha pertanto il periodo. 31 / 20 = 31 / (22 · 5 ) = (31 · 5)/ (22 · 52 ) = = 155 / 102 = 1.55 7 / 25 = 7 / 52 = (7 · 22)/ (22 · 52 ) = = 28 / 102 = 0.28 Numeri frazionari periodici • Esempio. 10 / 3 5/7 10 : 3 = 3. 3 3 3 ….. 10 1 1 ….. periodo 5 : 7 = 0.714285 714…… 50 10 30 20 60 40 5 ….. periodo Passaggi 2 2n • Un numero periodico in base 10 (ci sono fattori diversi da 2 e 5) è anche periodico in base 2. • Un numero non periodico in base 10 può essere periodico in base 2 (ad esempio 0.1, 0.4). • I numeri irrazionali, i reali che non si possono scrivere come quoziente di due interi, hanno una rappresentazione infinita e non periodica. Passaggi • 2n →2 : si espandono le cifre della base 2n in gruppi di n bit 16 = 24 → 2 • 2 →2n : si raggruppano le cifre della base 2 n a n 2 = 2 → 24 • Esempio. A91.2 16 = ? 2 = ? 8 A 9 1 . 2 1010 1001 0001 . 0010 5 2 2 1 . 1 Numeri frazionari periodici base 16 2 2n • Esempio. 2E0.FC 16 = ? 2 = ? 8 2 E 0 . F C 0010 1110 0000 . 1111 1100 1 3 4 0 . 7 7 base 16 base 2 base 8 base 2 base 8 22 Operazioni in base 2 Operazioni in base 2 • Addizione. 0+0=0 1+0=0+1= 1 1 + 1 = 1 (riporto) 0 Esempio. 1 5+ 5 21 0 Operazioni in base 2 • Sottrazione. 0-0=0 1-0=1 0 - 1 = 1 (prestito)0 - 1 = 1 Esempio. 1 1 1 1+ 1 0 1 1 0 11 10 10 Operazioni in base 2 • Moltiplicazione. 0×0=0 1×0=0×1=0 1 × 1 =1 con 1 si ricopia il numero con 0 spostamento a sinistra, poi si somma Esempio. 21 0 5 1 5 11 01 11 01 0 0 1 0 1 0 1 1 1 1 Operazioni in base 2 • Divisione. se il divisore ci sta, si mette 1; altrimenti 0 Esempio. 15 : 3 = 5 1111 : 11= 101 11 011 11 0 3 × 5 = 15 1 1× × 101 11 11-1111 Esercizio • Risolvere l’equazione 231x = 4510 vale a dire: determinare la base x in modo da soddisfare l’uguaglianza. • Soluzione. Utilizzando la formula di rappresentazione dei numeri abbiamo: 2 ⋅ x2 + 3 ⋅ x1 + 1 = 45 2x2 + 3x - 44 = 0 x1 = -11/2 x2 =4 Pertanto la soluzione è: x = 4. Verifica: 2 ⋅ 16 + 3 ⋅ 4 + 1 = 32 + 12 + 1 = 45 23 Esercizio Esercizio • Un matematico incontra un extraterrestre e gli pone il problema di trovare le soluzioni della seguente equazione: x2 - 13x + 36 = 0 • Soluzione. • La base 10 corrisponde al numero delle nostre dita. Per l’extraterrestre i numeri 1, 13, 36 sono nella base b che corrisponde al numero delle sue dita. Poiché la risposta è 5 e 6, che sono anche numeri della base 10, ricordando x2 – s x + p = 0 s = 11 e p = 30 si ha: 13b = 1110 36b = 3010 e pertanto b=8. L’extraterrestre dice che le soluzioni sono: 5 e 6. Quante dita ha l’extraterrestre? Rappresentazione degli interi Rappresentazione degli interi • Si hanno i seguenti tipi di dato byte, short, int, long corrispondenti ad un’occupazione di memoria rispettivamente di n =8, 16, 32, 64 bit segno numero 0 per lo zero e i positivi 1 per i negativi segno Rappresentazione degli interi • L’intervallo rappresentato è [ -2n-1 , 2n-1-1] • Rappresentazione di x ≥ 0 • Indichiamo con r(x) la rappresentazione binaria di x. • x=0 r(x) = 0 (tutti i bit 0) 0 00000…00000 Rappresentazione degli interi • x>0 quindi 0 < x ≤ 2n-1 -1 r(x) si ottiene dividendo x per 2 e prendendo i resti: r(x) = ck ck-1… c1 c0 2n-1 -1 …... 2 1 0 0 1111 …… 1111 …… 0 0000 …… 0010 0 0000 …… 0001 0 0000 …… 0000 0 00…ck ck-1 …c1 c0 se le cifre sono meno di n-1 si aggiungono degli 0 a sinistra 24 Rappresentazione degli interi Rappresentazione degli interi • Rappresentazione dell’intervallo dei numeri negativi. • Si rappresenta l’opposto di x > 0: -x < 0 Si definisce r(-x) in modo tale che sia: r(x) + r(-x) = 2n = 1 000…000 2 n Si dice che l’opposto di x è rappresentato in “complemento a 2”. • Vediamo un esempio con n=3 bit: [ -23-1, 23-1-1] = 3 0 11 = [ -22 , 22 -1] • Ci sono state anche altre scelte (complemento a 1); oggi è la rappresentazione degli interi in uso nella maggior parte dei calcolatori. 2 1 0 -1 -2 -3 -4 0 0 0 1 1 1 1 10 01 00 11 10 01 00 r(-1) = 2n – r(1) = 1000 – 001 = = 111 r(-2) = 2n – r(2) = 1000 – 010 = = 110 Rappresentazione degli interi Rappresentazione degli interi • Regola. • La regola si può esprimere in due modi diversi: si calcola r(x) e poi • Spiegazione. • Si sottrae una sequenza di n bit da 2n=100…00 (n+1 bit). • Finché si sottrae 0 da 0 il bit è 0 (quindi è uguale); quando si sottrae il primo 1 da 0, “scatta” il prestito 10-1=1 (il bit è uguale); con il prestito (che si propaga verso sinistra) il bit 0 diventa 1 (infatti, per poter fare 10 il bit 1 viene sottratto dal bit 0 subito a sinistra) e nella sottrazione da 1 il bit si inverte. • si invertono tutti i bit e si aggiunge 1 • si invertono tutti i bit tranne gli zero a destra e il primo 1 a destra. • Il bit del segno è trattato come gli altri bit. Rappresentazione degli interi • Questa regola è facile da realizzare. • In tale modo si fanno solo somme: - somma - differenza: si somma l’opposto; invece di a-b si fa r(a) + r(-b) - moltiplicazione: si sommano le righe - divisione: si fanno differenze e quindi somme Rappresentazione degli interi • Modularità degli interi e overflow. • Cosa accade se si esce dall’intervallo di rappresentazione? Il “trabocco” (overflow) è un errore logico non segnalato: il numero viene calcolato e si ripropone un valore dell’intervallo, come se l'intervallo si ripetesse. • Esempio. 2 + 2 = 4 (fuori dall’intervallo, n=3) 010 + 010 = 100 = r(-4) 25 Rappresentazione degli interi Rappresentazione degli interi • Quando si somma un numero con il suo opposto, la somma è una sequenza di bit tutti nulli e il bit 1 che “trabocca” dagli n bit si “trascura”: ad esempio con n=8 r(1) + r(-1) = 00000001 + 11111111 = 2n = = 100000000 il bit 1 si trascura nella somma (2n è 1 seguito da otto 0). È una conseguenza della definizione di r(-x) nel complemento a due. • Quando, invece, sommiamo 1 a 127 (2n-1-1), otteniamo: 127 + 1 = 128 ma 128 non appartiene all’intervallo. • Abbiamo: r(127) + r(1) = 01111111 + 00000001 = = 10000000 = r(-128) r(-128) è 1 seguito da sette 0; la somma di due positivi è un negativo !!!! overflow. Rappresentazione degli interi Rappresentazione degli interi • Aritmetica modulo m. • Siano a ed m due numeri naturali, indichiamo con a mod m il resto della divisione a/m • Esempio. Consideriamo i numeri 0, 4, 5, 9, 10, 15, 24, 30, 50, 55, ... abbiamo: Sia m = 10 abbiamo: per a < m a mod m = a per a ≥ m ritroviamo come resti i valori compresi tra 0 e 9 • 0/10 ha quoziente 0 e resto 0 e i numeri 10, 30, 50 hanno resto 0 (quozienti 1, 3, 5 rispettivamente) • 5/10 ha quoziente 0 e resto 5 e i numeri 15, 55 hanno resto 5 (quozienti 1 e 5) Rappresentazione degli interi Rappresentazione degli interi • Possiamo quindi raggruppare tutti i numeri che hanno lo stesso resto quando si esegue la divisione per m: l’insieme di questi numeri si chiama classe di equivalenza modulo m. • Se vogliamo rappresentare sempre i numeri con una sola cifra possiamo utilizzare le classi di equivalenza: 4 + 8 = 12 12 mod m = 2 (m=10) • Quando sommiamo due numeri rappresentati con una sola cifra la loro somma può essere ancora rappresentata da una sola cifra (es. 2+4=6) oppure da due cifre (es. 4+8=12). 4 + 8 = 2 in una aritmetica modulo m • Graficamente “trascuriamo” la cifra significativa che “trabocca” a sinistra. più 26 Rappresentazione degli interi Rappresentazione degli interi • Si può dimostrare che: • nell’addizione di due numeri in complemento a due si ha una situazione di overflow se e solo se: • si ha un riporto tra la colonna (n-1)-esima e la colonna n-esima e non si ha un riporto tra la colonna n-esima e la colonna (n+1)-esima (esempio: r(127) + r(1)) • non si ha un riporto tra la colonna (n-1)-esima e la colonna n-esima e si ha un riporto tra la colonna n-esima e la colonna (n+1)-esima • Il verificarsi di un overflow deriva dal fatto che si è “usciti” dall’intervallo di rappresentazione: [minint, maxint]. • Non c’è segnalazione di errore, perché viene costruito un “numero valido” ossia un numero della rappresentazione: ….. minint è come se l’intervallo maxint si ripetesse. Si …. …. parla di modularità minint della rappresentazione maxint …… degli interi. Rappresentazione degli interi • Nelle operazioni tra numeri interi la divisione per 0 è un errore. • Se si effettua una divisione a / b con a e b variabili intere e b=0 si genera un’eccezione che interrompe l’esecuzione del programma: ArithmeticException Esponente intero dei reali Esponente intero dei reali Esponente intero dei reali • I reali sono rappresentati con mantissa ed esponente. Tale esponente è intero. • La rappresentazione per l’esponente intero segue una regola diversa: l’esponente e è rappresentato in traslazione (con eccesso): • L'esponente traslato r(e) appartiene all'intervallo [0, 2n -1] dove n è il numero di bit della sua rappresentazione. • Viene perciò memorizzato r(e) invece di e eccesso 2n-1 pari 2n-1 -1 dispari k = e r(e) = e + k 27 Esponente intero dei reali • Negli anni ci sono state varie scelte per la rappresentazione dei reali. Per i reali in semplice precisione: • Vediamo un esempio con n=3 bit: max min 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0 Esponente intero dei reali [min, max] corrispondono a [-4, 3] k = 2n-1 [-3, 4] k = 2n-1-1 dispari pari • base 16, esponente in 7 bit, eccesso pari 64 (sui grossi calcolatori); con l’eccesso pari vale ancora la regola che r(x) + r(-x) = 2n. • base 2, esponente in 8 bit, eccesso dispari. • Lo standard IEEE (Institute for Electrical and Electronics Engeneering) nel 1985 ha scelto per i Personal Computer (ora in uso nella maggior parte dei calcolatori): base 2, eccesso dispari. Esponente intero dei reali • Nel caso dei float (32 bit) l’eccesso k = 27 -1 = 127. • L’intervallo rappresentato è [0, 255] che corrisponde all’intervallo (vero, non traslato) [-127, 128]. 255 11111111 128 • In binario: 254 … 2 1 0 11111110 127 …. …. -125 00000001 -126 00000000 -127 28