From:"www.xxxxwebmaster.cjb.net" <xxxxwebmaster@xxxxxxxxxxxxxxxxxxxxx> Subject: Re: holaz :) Date: Wed, 8 May 2002 13:56:43 +0200 --- La seguente mail e' in riferimento al thread che si trova all'indirizzo: http://www.alground.com/forum/viewthread.php?tid=229 La domanda mi era stata rivolta anche in privato, ma posto qui perche' interessa anche il forum di Alground che se lo ritiene utile potra' pubblicare questa risposta nel suo forum. La mia mail e' in prima persona rivolta ad XXXX perche' e' quello che ha iniziato il thread. --- Mi fa piacere che tu ti ricordi quello che ti ho detto un po' di tempo fa, ossia che la cosa piu' importante per un listato e' che ALMENO faccia quello che vuoi, ma un minimo di "dignita'" nel codice bisognerebbe sempre mettercela :) Vediamo di focalizzare alcuni punti che, secondo me, sono il fattor comune di tutti i tuoi listati. 0. Se guardiamo con attenzione il tuo listato, ci accorgiamo che tu hai bisogno di 7 variabili, di cui 6 dello stesso tipo (float). Come mai tu ne utilizzi 27? :) Una possibile struttura dati da utilizzare potrebbe essere: #define MAX_LEN 50 /* il tuo 389 mi pare esagerato :) */ #define VOCI 6 typedef enum {Reddito_Imponibile, Oneri_Deducibili, Detrazioni_Imposta, Imposta_Netta, Ritenute_Subite, Imposta_Dovuta } Nome_Voce; typedef float Importo[VOCI]; typedef struct Info { char nome[MAX_LEN]; Importo valore; } Param; typedef struct Info *Options; In modo che poi accedi ai vari campi con la "coscienza" di quello che stai facendo; ad esempio: Options cliente = NULL; /* allochi la struttura */ cliente->nome; cliente->valore[Reddito_Imponibile]; /* Indicano campi ben precisi, facili ed intuitivi da capire */ Almeno elimini tutta la strage di variabili che hai fatto tu :) 1. Perche' aprire il file all'inizio se poi ci scrivi solo alla fine? In questo modo se l'apertura fallisse, ti toglieresti anche la possibilita' di vedere a video i risultati. 2. Il logo celebrativo del programmatore, mettilo in una procedura cosi' lo richiami ogni volta che serve; fai si che la procedura accetti in input un "FILE *" e se vuoi stamparlo sullo schermo gli passi "stdin", mentre se ti serve stamparlo su file gli passi il puntatore alla struttura di tipo "FILE *" appunto. 3. Quando utilizzi la scanf, per leggere una stringa non necessiti dell'operatore '&' perche' gli operandi '&*' si annullano a vicenda (se hai un minimo di famigliarita' con i puntatori ti e' facile capire perche'). 4. Consideriamo questo pezzo del tuo codice: --- if(med <= 129.11) med1=0; else med1 = med-129.11; --- Tutto cio' si puo' risolvere con una macro e, visto che esegui una sottrazione ed un controllo sul medesimo valore, restituendo eventualmente zero, il controllo puo' essere tranquillamente un minore stretto. Nel senso: se il primo controllo fosse un minore stretto e "med" fosse proprio "129.11", il controllo fallisce e tu ritorni "med1 = med-129.11" che e' esattamente zero (ossia quello che volevi tu). Assicurati che i tuoi controlli non siano ridondanti. Potresti usare una cosa del tipo: #define LIMITE 129.11 #define MEDICHE(x) ( (x < LIMITE) ? 0 : (x - LIMITE) ) 5. Ragioniamo ora sul calcolo delle varie aliquote per scaglioni. Cosi' come hai scritto il codice tu saltano agli occhi subito un po' di difetti: a) Utilizzi costanti all'interno del codice senza assegnarle dei nomi; cio' significa che se un giorno dovrai modificare il codice, dovrai spulciarlo dall'inizio alla fine ricercando tutte le occorrenze e cambiarle. b) Molte parti del tuo codice sono ripetitive; occorre automatizzare con funzioni matematiche ben definite. c) Fai uso di un numero di variabili spropositato, del tutto inutili; a parte il puro folclore non c'e' nessuna spiegazione (razionale) a tutto cio'. d) ecc. ecc. Esaminiamo questo frammento del tuo codice: --- if(redr <= 10329.14) irpef1= redr*18/100; else irpef1=1859.25; --- Questa procedura e quella per il calcolo dell'aliquota piu' alta sono diverse da tutte le altre che risultano, invece, avere tutte una matrice comune. Tenendo presente quello che ti ho detto prima questo problema si puo' risolvere (tra i vari modi) con questa macro: #define ALIQUOTA_MIN 18 #define ALFA_MIN 10329.14 #define BETA_MIN 1859.25 #define IRPEF_MIN(x) ( (x <= ALFA_MIN) ? (x * ALIQUOTA_MIN / 100) : BETA_MIN ) Sembra essere abbastanza Chiara (o Marta se preferisci): no? Se e quando cambieranno i "limiti" ma non il modo di calcolare l'irpef ti bastera' agire sulle aliquote e su ALFA_MIN e BETA_MIN senza toccare una riga di codice. Esaminiamo ora le altre procedure, esclusa quella per il calcolo dell'aliquota massima; sono tutte del tipo: --- ali(n)=redr-alfa(n); if (ali(n) <beta(n)) ali(n)=redr-alfa(n); if (ali(n) >=beta(n)) ali(n) = beta(n); if (ali(n) <=0) ali(n)=0; irpef(n)=ali(n)*aliq/100; --- Le variabili della serie "ali(n)" non servono assolutamente a nulla ed ora vediamo di dimostrarlo :) Esaminiamo un generico caso; tu imponi che: ali = redr - alfa; E poi ti chiedi: Se "ali < beta" allora "ali = redr - alfa" Allora io mi chiedo: chi e' "ali"? Beh "ali" e' "redr - alfa" a detta di XXXX; allora sostituendo, la condizione logica diventa: Se "redr - alfa < beta" allora /* bla bla bla */ Con un misero quanto banale passaggio matematico, si conclude che la condizione corretta e': Se "redr < (alfa + beta)" allora /* bla bla bla */ Continuando nel ragionamento, ci accorgiamo che il tuo secondo "if" e' in realta' un "else" del primo "if"... Chiaro no? Se non e' minore di una cosa allora puo' essere solo maggiore/uguale (i saggi, che di analisi matematica non sapevano nulla, dicevano "se non e' zuppa e' pan bagnato") :) Quindi lo tralasciamo perche' non ci serve per ora; l'ultimo "if" invece; Se "ali <= 0" allora /* bla bla bla */ Solita cosa... ma chi e' "ali"? Sempre lui, per cui sostituendo: "redr - alfa <= 0" implica che "redr <= alfa" (piu' tardi ti faro' notare che questa condizione e' ridondante). Le condizioni logiche che abbiamo fino ad ora sono: Se "redr < (alfa + beta)" allora /* bla bla bla */ else /* bla bla bla */ Se "redr <= alfa" allora /* bla bla bla */ Che cosa puoi notare? L'ordine delle condizioni non e' corretto a livello logico, infatti la prima condizione ci dice se "redr" e' minore di "alfa+beta", ma non ci assicura che sia anche minore di "alfa" (in pratica ci fornisce una condizione necessaria ma non sufficiente, perche' la seconda e' piu' restrittiva). Per tanto il flusso logico corretto sarebbe: Se "redr <= alfa" allora "zero" altrimenti Se "redr < (alfa + beta)" allora "redr - alfa" altrimenti "beta" Come detto ora ti faccio notare che il primo controllo logico e' ridondante, in quanto definito gia' nel caso 2; immagina di mettere un minore stretto al primo controllo e che "redr" sia proprio uguale ad "alfa". Cio' che succede e' che il primo controllo fallisce, mentre il secondo e' verificato (per ogni beta maggiore di zero; ma se fosse zero si andrebbe al terzo controllo che restiruisce beta, cioe' zero e cioe' giusto :P), per cui si ritorna "redr - alfa"; ma "redr" e' uguale ad "alfa" per ipotesi, per cui restituisce "zero" :) Concludendo il flusso corretto risulta: Se "redr < alfa" allora "zero" altrimenti Se "redr < (alfa + beta)" allora "redr - alfa" altrimenti "beta" In sistanza il tutto si puo' risolvere con una macro, del tipo: #define IRPEF_MED(x,y,z,w) ( (x < y) ? 0 : ( (x < ( y + z)) ? ((x - y)* w / 100) : (z * w / 100)) ) Dove passerai, al posto della 'x' il "redr", 'y' ed 'z' i rispettivi "alfa" e "beta" ed al posto di 'w' l'aliquota relativa allo scaglione. In sostanza potresti definire una cosa del tipo: #define ALIQUOTA_1 24 #define ALFA_1 10329.14 #define BETA_1 5164.56 #define ALIQUOTA_2 32 #define ALFA_2 15493.72 #define BETA_2 15493.69 #define ALIQUOTA_3 39 #define ALFA_3 30987.41 #define BETA_3 38734.27 Questa macro e' definita per tutti questi scaglioni (e per eventuali futuri strutturati nella stessa maniera). Ultimo caso da analizzare e' il calcolo dell'aliquota massima; il tuo codice recita: --- ali5=redr-69721.41; if (ali5 <= 0) ali5=0; if (ali5 >69721.41) ali5 = redr - 69721.41; irpef5=ali5*45/100; --- Questo caso e' analogo al calcolo dell'aliquota piu' bassa (ma non uguale purtroppo). Pero' vorrei farti notare una cosa: a che cosa ti serve il secondo "if"? Assegni ad un "ali5" un valore; poi dici che se e' maggiore di una data quantita' gli riassegni lo stesso valore; perche' prima che valore c'era? :) L'unico "if" che ha senso e' il primo; per tanto potresti risolvere il problema nella seguente maniera: #define ALIQUOTA_MAX 45 #define ALFA_MAX 69721.41 #define IRPEF_MAX(x) ( ( (x - ALFA_MAX) < 0) ? 0 : ((x - ALFA_MAX) * ALIQUOTA_MAX / 100) ) Anche questa mi sembra molto chiara e non necessita di ulteriori commenti. 6. Risulta inutile dichiarare 300.000 variabili per i sub-totali, quando te ne bastano, esagerando, 2 per ottenere lo stesso risultato. La variabile che contiene il totale, puo' anche contenere i sub-totali, poiche' una sommatoria come la tua e' cosi' fatta: x1 + x2 + x3 + ... +xn Se io scrivessi che (x1 + x2) = y e dicessi: y + x3 + ... + xn sei d'accordo che il risultato non cambia? Ho detto esagerando 2 perche' se non ti serve printare a video i sub-totali (o ti serve ma non vuoi richiamare 2 volte la stessa macro), utilizzi una variabile temporanea che usi per printare a video e per sommarla al totale. Nella fattispecie, per il tuo codice, una cosa del tipo: --- temp = IRPEF_MIN(redr); tot += temp; printf("\nIrpef 18: %.2f", temp); temp = IRPEF_MED(redr, ALFA_1, BETA_1, ALIQUOTA_1); tot += temp; printf("\nIrpef 24: %.2f", temp); temp = IRPEF_MED(redr, ALFA_2, BETA_2, ALIQUOTA_2); tot += temp; printf("\nIrpef 32: %.2f", temp); temp = IRPEF_MED(redr, ALFA_3, BETA_3, ALIQUOTA_3); tot += temp; printf("\nIrpef 39: %.2f", temp); temp = IRPEF_MAX(redr); tot += temp; printf("\nIrpef 45: %.2f", temp); printf("\nIrpef totale: %.0f\n\n", tot); --- Ottieni lo stesso risultato con un terzo delle righe che hai scritto tu :) Se poi addirittura non ti servisse neanche avere i risultati intermedi (per fare una cosa cosi', ricordati di inizializzare a zero "tot"), puoi tranquillamente eliminare "temp", e sommare tutto direttamente a "tot". 7. Poiche' utilizzi la "conio.h", la chiamata a: system("PAUSE"); La puoi rimpiazzare con una "getch();" mentre la: system("CLS"); con una clrscr(); Il Borland ti dava dei warning per le chiamate "system" perche' essa e' definita in "stdlib.h" che tu NON hai incluso :) 8. Il devC++ ti da quei problemi, come ti ho gia' detto, perche' la "conio.h", non e' una libreria "standard", per cui un compilatore non e' tenuto ad averla e linkarla. Inoltre, da specifica, non deve essere utilizzata per applicazioni a 32-bit, per via del fatto che utilizza chiamate a basso livello e OS a 32-bit in modalita' protetta non amano cio' (niente interrupt... a priori; anche se, ad esempio, Linux ha un interrupt INT80h con svariate funzioni da poter essere usato), con risultato il crash del programma. Di fatto sospetto, che non sia neanche presente. Per ovviare a cio' potresti utilizzare la codifica ANSI dei colori, a patto che l'utilizzatore del tuo programma carichi nel suo config.sys il driver "ansi.sys". 9. Quando chiami la cprintf("\r"); solo per questo, e' uno spreco; inserisci uno '\r' in piu' nella prossima riga che stampi a video ed ottieni lo stesso risultato con una chiamata a funzione in meno. Questo e' quanto e scusa se ti ho scritto solo ora, ma come sai durante la settimana sono molto impegnato. Alla prossima. Ciao. -- Nulla contribuisce alla serenita' dell'anima quanto il non avere nessuna opinione.