Esercizio C
ho questo esercizio che dice:
Scrivere una funzione f che, ricevuti due interi b e n, restituisca la cifra più significativa di n,quando questo è rappresentato in base b.E' richiesto di scrivere la funzione in al massimo 4 righe.Ogni riga può contenere una sola istruzione,dichiarazione,o intestazione di ciclo.
esempio
f(321,10) ritorna 3 perchè 3 è la cifra più significativa di 321 in base 10
f(27,3) rritorna 1 perchè 27 in base 3 è 1000 e 1 è la cifra più significativa
io ho trovato un modo per trasformare un numero n in base b ma poi non so come fare per salvare il resto al contrario e andare a vedere la cifra più significaiva del resto.
però già questo altro che 4 righe che mi servono poi
Scrivere una funzione f che, ricevuti due interi b e n, restituisca la cifra più significativa di n,quando questo è rappresentato in base b.E' richiesto di scrivere la funzione in al massimo 4 righe.Ogni riga può contenere una sola istruzione,dichiarazione,o intestazione di ciclo.
esempio
f(321,10) ritorna 3 perchè 3 è la cifra più significativa di 321 in base 10
f(27,3) rritorna 1 perchè 27 in base 3 è 1000 e 1 è la cifra più significativa
io ho trovato un modo per trasformare un numero n in base b ma poi non so come fare per salvare il resto al contrario e andare a vedere la cifra più significaiva del resto.
int f(int n, int b) { int r=0; int i; for(i=0;i<n;i++) { n/=b; r=n%b; } return r; }
però già questo altro che 4 righe che mi servono poi
Risposte
magnifico grazie mille siete stati ottimi

"claudio86":
Ho l'impressione che usare unsigned int sia quasi più sbagliato che usare signed int. Sicuramente con numeri negativi la funzione così com'è non va, quindi ci sono due possibilità:
- documentare che la funzione si aspetta numeri positivi e b > 1;
- controllare che i numeri siano positivi e in caso contrario restituire un errore.
Usare unsigned invece introduce un altro possibile errore:
#include <iostream> void f(unsigned int a) { std::cout << a << '\n'; } int main() { signed int a = -9; f(a); }
compilato con g++ -std=c++98 -pedantic -Wall -Wextra, non genera neppure un warning, e sul mio computer stampa 4294967287.
Un comportamento accettabile se tu passassi un valore n < 0 alla funzione sarebbe quello di calcolare la cifra più significativa di -n (cioè 9). Ma in questo caso c'è overflow, -n viene convertito in MAX_UINT - n, e il risultato è la cifra più significativa di MAX_UINT - n (cioè 4), che invece è sicuramente sbagliato.
Usando unsigned non si evitano problemi quando n < 0, anzi si ottiene un risultato valido ma sbagliato.
Il prototipo della funzione della funzione con i tipi degli argomenti fa necessariamente parte della documentazione di una funzione. Se la funzione richiede il passaggio di un valore senza segno, come puoi aspettarti che funzioni correttamente se gli viene passato un valore con segno? Che differenza c'è quindi tra questo e lo scrivere che la funzione accetta solo valori positivi e valori di $b > 1$? Non vengono generati warning né errori anche in questo caso ed è necessario trovare un modo opportuno per segnalare l'errore in fase di input. Forse un assert oppure restituire un numero negativo forse? In entrambi i due casi la soluzione non è migliore di quella di ignorare la possibilità di valori negativi completamente. Se infatti l'argomento ha tipo unsigned, alla funzione viene passato SEMPRE un valore senza segno e non ha importanza se questo valore è stato passato da un utente poco attento che era per qualche strana ragione convinto che la funzione dovesse funzionare anche con tipi signed.
"apatriarca":
Il prototipo della funzione della funzione con i tipi degli argomenti fa necessariamente parte della documentazione di una funzione. Se la funzione richiede il passaggio di un valore senza segno, come puoi aspettarti che funzioni correttamente se gli viene passato un valore con segno?
In linea di principio hai ragione, però una nota esplicita sarebbe molto più chiara.
Che differenza c'è quindi tra questo e lo scrivere che la funzione accetta solo valori positivi e valori di $b > 1$? Non vengono generati warning né errori anche in questo caso ed è necessario trovare un modo opportuno per segnalare l'errore in fase di input. Forse un assert oppure restituire un numero negativo forse? In entrambi i due casi la soluzione non è migliore di quella di ignorare la possibilità di valori negativi completamente.
Invece potrebbe essere. Se b è negativo allora alla seconda iterazione anche n è negativo. Se entrambi sono < 0 (e quasi sicuramente |n| >> |b|) allora la funzione restituirà subito n, che non è una cifra. Ci si accorge del problema non appena si chiama f() con valori negativi.
Se invece la funzione accetta un unsigned allora restituisce sempre una cifra, anche se è sbagliata (e potrebbe non essere così ovvio che è sbagliata).
Inoltre se la funzione accetta un signed allora si può controllare se sia positivo o meno.
Se infatti l'argomento ha tipo unsigned, alla funzione viene passato SEMPRE un valore senza segno e non ha importanza se questo valore è stato passato da un utente poco attento che era per qualche strana ragione convinto che la funzione dovesse funzionare anche con tipi signed.
Invece ha molta importanza. Se dentro la funzione n = 4294967287, il client intendeva proprio quel valore oppure intendeva -9? Non c'è nessun modo per saperlo. Se invece la funzione accetta un intero con segno si può controllare se il client ha sbagliato o no.
Il prototipo è un contratto, se il client decide volontariamente di ignorarlo è a suo rischio e pericolo. Il comportamento della funzione è inoltre completamente evidente dal tipo dei suoi parametri. E' ovvio che se il tipo del parametro è unsigned, un eventuale valore negativo passato come argomento verrà convertito in un unsigned molto diverso (e questo non l'ha deciso l'autore della funzione ma gli autori del linguaggio). Non esiste alcuna ambiguità nel comportamento della funzione quando gli viene passato un valore negativo. E' come prendersela con chi ha inventato le forchette se l'utente è convinto che funzionano anche tenute al contrario. Per iterare questo punto:
Non ha alcuna importanza cosa intendeva l'utente. E' 4294967287 il valore che ha passato alla funzione. E' ovviamente possibile che sia un errore, ma non è certamente in qualche ambiguità sul comportamento della funzione.
Al contrario, se i parametri sono di tipo signed, è naturale aspettarsi che la funzione sia valida per ogni tipo di valore di ingresso ed è necessario documentare con grande attenzione il suo comportamento cercando di evidenziare che per alcuni valori il comportamento della funzione non è definito. E' MOLTO più facile ignorare una parte della documentazione (dopotutto quanti di quelli che usano o hanno usato la funzione scanf ne hanno letto la documentazione?) che ignorare il tipo di un parametro.
EDIT: Facendo tralaltro uso di una analizzatore statico del codice (o di un compilatore più restrittivo nei sui warning) si ottiene comunque qualcosa come il seguente passando un int al posto di un valore unsigned:
Invece ha molta importanza. Se dentro la funzione n = 4294967287, il client intendeva proprio quel valore oppure intendeva -9? Non c'è nessun modo per saperlo. Se invece la funzione accetta un intero con segno si può controllare se il client ha sbagliato o no.
Non ha alcuna importanza cosa intendeva l'utente. E' 4294967287 il valore che ha passato alla funzione. E' ovviamente possibile che sia un errore, ma non è certamente in qualche ambiguità sul comportamento della funzione.
Al contrario, se i parametri sono di tipo signed, è naturale aspettarsi che la funzione sia valida per ogni tipo di valore di ingresso ed è necessario documentare con grande attenzione il suo comportamento cercando di evidenziare che per alcuni valori il comportamento della funzione non è definito. E' MOLTO più facile ignorare una parte della documentazione (dopotutto quanti di quelli che usano o hanno usato la funzione scanf ne hanno letto la documentazione?) che ignorare il tipo di un parametro.
EDIT: Facendo tralaltro uso di una analizzatore statico del codice (o di un compilatore più restrittivo nei sui warning) si ottiene comunque qualcosa come il seguente passando un int al posto di un valore unsigned:
main.c(14,4): Function f expects arg 1 to be unsigned int gets int: a
"apatriarca":
Il prototipo è un contratto, se il client decide volontariamente di ignorarlo è a suo rischio e pericolo.
Mi sembra veramente difficile che il client lo ignori volontariamente. Nella stragrande maggioranza dei casi lo fa accidentalmente, e sarebbe meglio ridurre questa possibilità al minimo.
Non ha alcuna importanza cosa intendeva l'utente. E' 4294967287 il valore che ha passato alla funzione. E' ovviamente possibile che sia un errore, ma non è certamente in qualche ambiguità sul comportamento della funzione.
Insomma, io la vedo in modo diverso. L'obiettivo non è giustificarsi dicendo "È colpa dell'utente!" quando c'è un errore, ma ridurre al minimo gli errori (anche perché spesso l'utente è la stessa persona che ha scritto la funzione, quindi dovrebbe essere interessato anche lui a capire dove sono gli errori).
Prendi la funzione di questo thread. In entrambi i casi l'algoritmo funziona solo per valori positivi.
Caso 1: la funzione accetta signed int
- Se passi un valore negativo l'algoritmo potrebbe restituire un valore sbagliato, oppure un errore (crash, ciclo infinito...).
Caso 2: la funzione accetta unsigned int
- Se passi un valore negativo questo viene convertito in positivo, quindi l'algoritmo restituisce sempre un valore (quasi sicuramente sbagliato).
Nessuno dei due casi va bene se il numero passato è negativo.
Già accorgersi che l'algoritmo non funziona sempre non è banale, soprattutto nel secondo caso: l'algoritmo restituisce sempre un valore e potrebbe non essere evidente che sia un valore sbagliato. Nel primo caso invece se un valore genera un errore c'è sicuramente qualcosa che non va (anche se non generasse mai errori non sarebbe comunque meglio del primo caso).
Una volta capito il problema vogliamo inserire un controllo dentro la funzione per verificare se il numero passato è positivo (e in caso contrario restituire un errore o eseguire un altro algoritmo).
Nel caso 1 è banale.
Nel caso 2 è impossibile. Il controllo deve essere fatto dall'utente, il che vuol dire:
- sparpagliare il controllo in tutti i punti in cui viene chiamata la funzione (che è esattamente il contrario dello scopo di una funzione);
- dimenticare il controllo in qualche punto, quindi non aver risolto il problema.
Non vedo alcun vantaggio ad usare unsigned invece che signed, anzi mi sembra molto più problematico. A mio parere vince signed.
EDIT: Facendo tralaltro uso di una analizzatore statico del codice (o di un compilatore più restrittivo nei sui warning) si ottiene comunque qualcosa come il seguente passando un int al posto di un valore unsigned:
Sì, ma deve farlo l'utente e non c'è garanzia che lo faccia.
Se una funzione accetta solo valori senza segno, ACCETTA SOLO NUMERI POSITIVI. Non esiste il caso del numero negativo. Che poi.. seguendo il tuo discorso allora definiremmo tutte le variabili come long double. Anche i numeri floating point possono infatti essere convertiti, senza alcun warning, ad un intero (con o senza segno) e qualche utente disattento potrebbe decidere che è una buona idea passare \(-2^{128} + 0.125\) alla funzione e aspettarsi il valore corretto in output.
Vedila così allora. Se definisci la funzione usando gli int hai più del 50% di valori inaccettabili come valori possibili in ingresso, se la definisci usando gli unsigned hai solo i casi \(b \in \{0, 1\}\). A me sembra MOLTO chiaro quale delle due funzioni preferire. L'uso dei tipi come documentazione e misura di sicurezza è considerato MOLTO importante in praticamente qualsiasi guida di stile. Ci sono dei linguaggi, non C ovviamente, che arrivano a garantire l'assenza di errori a runtime grazie all'uso di un type system molto più avanzato e potente. Quanto più ci si avvicina a questo risultato meglio è per me.
Vedila così allora. Se definisci la funzione usando gli int hai più del 50% di valori inaccettabili come valori possibili in ingresso, se la definisci usando gli unsigned hai solo i casi \(b \in \{0, 1\}\). A me sembra MOLTO chiaro quale delle due funzioni preferire. L'uso dei tipi come documentazione e misura di sicurezza è considerato MOLTO importante in praticamente qualsiasi guida di stile. Ci sono dei linguaggi, non C ovviamente, che arrivano a garantire l'assenza di errori a runtime grazie all'uso di un type system molto più avanzato e potente. Quanto più ci si avvicina a questo risultato meglio è per me.
Un intero senza segno è prima di tutto un intero. È possibile che il chiamante usi un tipo con segno nei suoi calcoli e poi lo passi alla funzione. Molto più improbabile che usi numeri in virgola mobile.
Se usi tipi con segno puoi accorgerti dell'errore e prendere provvedimenti (cambiare algoritmo, lanciare un'eccezione, assert, log...), se usi tipi senza segno no.
Tra i vari articoli contro l'uso di unsigned ho trovato questo esempio:
Hai una funzione che legge dati da un file e restituisce il numero di elementi letti (quindi un valore sempre positivo). Qual è il tipo del valore restituito?
Caso 1: signed int
Se restituisce -10 sicuramente c'è un errore nell'implementazione => errore trovato.
Caso 2: unsigned int
Restituisce sempre un valore attendibile, se anche c'è un errore nell'implementazione non è evidente => errore tralasciato.
(nota che la funzione non restituisce un errore)
Mi è capitato molte volte di vedere sconsigliato l'uso di unsigned (o almeno dibattuto, sicuramente "si usa quando si sa che una variabile assumerà solo valori positivi" non è un'argomentazione sufficiente):
http://stackoverflow.com/questions/336/ ... igned-ones
http://bytes.com/topic/c/answers/459856 ... s-unsigned
http://eli.thegreenplace.net/2010/06/11 ... ion-in-cc/
Comunque vedo che abbiamo opinioni diverse.
Se usi tipi con segno puoi accorgerti dell'errore e prendere provvedimenti (cambiare algoritmo, lanciare un'eccezione, assert, log...), se usi tipi senza segno no.
Tra i vari articoli contro l'uso di unsigned ho trovato questo esempio:
Hai una funzione che legge dati da un file e restituisce il numero di elementi letti (quindi un valore sempre positivo). Qual è il tipo del valore restituito?
Caso 1: signed int
Se restituisce -10 sicuramente c'è un errore nell'implementazione => errore trovato.
Caso 2: unsigned int
Restituisce sempre un valore attendibile, se anche c'è un errore nell'implementazione non è evidente => errore tralasciato.
(nota che la funzione non restituisce un errore)
Mi è capitato molte volte di vedere sconsigliato l'uso di unsigned (o almeno dibattuto, sicuramente "si usa quando si sa che una variabile assumerà solo valori positivi" non è un'argomentazione sufficiente):
http://stackoverflow.com/questions/336/ ... igned-ones
http://bytes.com/topic/c/answers/459856 ... s-unsigned
http://eli.thegreenplace.net/2010/06/11 ... ion-in-cc/
Comunque vedo che abbiamo opinioni diverse.
In nessuno dei tre link che hai mandato c'è un largo consenso sull'uso di interi signed al posto di unsigned. Ci sono sempre le stesse due posizioni che abbiamo discusso anche in questo thread. Sono certamente d'accordo che ci sono situazioni in cui l'uso di unsigned può portare a problemi, ma lo stesso vale anche per i tipi signed (per esempio rende molto più complicate le operazioni bitwise e in generale ha operazioni che sono indefinite). Non credo che la tua posizione su questo semplice esempio abbia però senso. Non credo infatti che abbia senso aspettarsi che qualcuno voglia passare un valore negativo quando prototipo e documentazione dicono il contrario (vedi commento alla fine).
Il motivo perché in questo caso ha senso usare un tipo signed è in realtà proprio che l'autore non aveva riconosciuto l'effettivo insieme di valori che la funzione può restituire. Una funzione che legge da file può infatti fallire per una miriade di ragioni per cui l'effettivo insieme di valori restituibili sono il numero di byte letti oppure un qualche errore. Per questa ragione ha senso usare i tipi signed. Perché c'è spazio per l'errore (e zero non è in generale abbastanza). Se però una funzione di questo tipo non potesse fallire (al momento non mi viene in mente niente di questo tipo che non può fallire ma supponiamo che ci sia) allora credo che dovrebbe restituire unsigned perché non si avrebbe alcun vantaggio ad avere dei valori in più tra quelli di ritorno.
Si potrebbe comunque discutere se questo tipo di design sia buono o meno per questo genere di funzioni. E' quello seguito dallo standard C, ma in generale credo che porti spesso o ad ignorare i valori di ritorno oppure a riempire il codice di if. La soluzione adottata dal C++ non è comunque a mio parere poi tanto meglio. Ma ammetto di non avere ancora trovato una soluzione definitiva al problema (ameno in linguaggi simil-C).
C'è comunque sicuramente una sola opinione su questa faccenda su cui tutti sono d'accordo: MAI mischiare unsigned e signed. Le regole di promozioni tra tipi sono tra le cose più complicate e assurde che ci sono nel C. Basti dire che viene preferita la promozione ad unsigned.. La scelta di inserire questi cast automatici era motivata dalla possibilità di inserire meno cast possibili, ma credo che sia stato un enorme errore in quanto non sono sempre quello che l'autore del codice vorrebbe e possono portare ad errori molto difficili da trovare. Se in un particolare codice si fa molto uso di interi con segno allora è forse meglio continuare ad usare gli interi con segno (a meno che l'uso sia effettivamente separato e ci siano buone ragioni per preferire gli unsigned - ma allora forse c'è da verificare anche le ragione del resto).
Ma in realtà il problema è inesistente in quanto non è vero che il valore della funzione non è definito quando $n$ è negativo. Per $b$ negativo si potrebbero trovare soluzioni, ma sinceramente non credo che abbiano molto senso. La seguente è probabilmente la migliore soluzione al problema:
P.S. Riguardo all'ultimo link sui cicli.. Ho una regola per cui non faccio MAI un ciclo con contatore unsigned che va verso lo zero. Se un ciclo va verso il basso allora il contatore usato deve essere signed.
Hai una funzione che legge dati da un file e restituisce il numero di elementi letti (quindi un valore sempre positivo). Qual è il tipo del valore restituito?
Il motivo perché in questo caso ha senso usare un tipo signed è in realtà proprio che l'autore non aveva riconosciuto l'effettivo insieme di valori che la funzione può restituire. Una funzione che legge da file può infatti fallire per una miriade di ragioni per cui l'effettivo insieme di valori restituibili sono il numero di byte letti oppure un qualche errore. Per questa ragione ha senso usare i tipi signed. Perché c'è spazio per l'errore (e zero non è in generale abbastanza). Se però una funzione di questo tipo non potesse fallire (al momento non mi viene in mente niente di questo tipo che non può fallire ma supponiamo che ci sia) allora credo che dovrebbe restituire unsigned perché non si avrebbe alcun vantaggio ad avere dei valori in più tra quelli di ritorno.
Si potrebbe comunque discutere se questo tipo di design sia buono o meno per questo genere di funzioni. E' quello seguito dallo standard C, ma in generale credo che porti spesso o ad ignorare i valori di ritorno oppure a riempire il codice di if. La soluzione adottata dal C++ non è comunque a mio parere poi tanto meglio. Ma ammetto di non avere ancora trovato una soluzione definitiva al problema (ameno in linguaggi simil-C).
C'è comunque sicuramente una sola opinione su questa faccenda su cui tutti sono d'accordo: MAI mischiare unsigned e signed. Le regole di promozioni tra tipi sono tra le cose più complicate e assurde che ci sono nel C. Basti dire che viene preferita la promozione ad unsigned.. La scelta di inserire questi cast automatici era motivata dalla possibilità di inserire meno cast possibili, ma credo che sia stato un enorme errore in quanto non sono sempre quello che l'autore del codice vorrebbe e possono portare ad errori molto difficili da trovare. Se in un particolare codice si fa molto uso di interi con segno allora è forse meglio continuare ad usare gli interi con segno (a meno che l'uso sia effettivamente separato e ci siano buone ragioni per preferire gli unsigned - ma allora forse c'è da verificare anche le ragione del resto).
Ma in realtà il problema è inesistente in quanto non è vero che il valore della funzione non è definito quando $n$ è negativo. Per $b$ negativo si potrebbero trovare soluzioni, ma sinceramente non credo che abbiano molto senso. La seguente è probabilmente la migliore soluzione al problema:
int f(int n, int b) { assert(b > 1); /* qui si può dibattere su quale possa essere la soluzione migliore.. * non ho voglia di pensare a quale valore restituire e sono in C per cui * scelgo di usare gli assert ma diverse soluzioni diverse sono possibili. */ if (n < 0) n = -n; /* le cifre sono per me sempre positive per cui nego il numero. * Possibile comportamento indefinito per MIN_INT, la soluzione * potrebbe essere quella di fare un cast in un tipo più grande. */ for ( ; n >= b; n /= b) {} /* preferisco scrivere il blocco vuoto con le due parentesi. */ return n; }
P.S. Riguardo all'ultimo link sui cicli.. Ho una regola per cui non faccio MAI un ciclo con contatore unsigned che va verso lo zero. Se un ciclo va verso il basso allora il contatore usato deve essere signed.
Nel codice precedente ho scritto che nel caso in cui $n$ fosse MIN_INT il comportamento della funzione era indefinito e consigliavo nel commento di passare ad un tipo intero più grande. Ma non è sempre possibile o desiderabile farlo. Per cui presento un metodo alternativo che passa però per un tipo unsigned. Infatti, se $n$ è negativo è sempre possibile memorizzare $-n$ all'interno di un intero senza segno della stessa dimensione. E' però necessario stare attenti a non fare operazioni indefinite (non si può infatti scrivere semplicemente $-n$ come nel codice precedente o si caderà nella stessa situazione). Questa è la mia soluzione (con qualche commento in meno):
Certamente non è particolarmente elegante, ma dovrebbe essere sempre ben definito e corretto. E' però necessario verificare che il valore restituito sia positivo.. non esattamente l'ideale. Forse qualcosa come le eccezioni o un assert personalizzato sarebbero preferibili.
int f(int n, int b) { unsigned int un = 0, ub = b; if (b < 2) { return -1; /* questa volta consideri i numeri negativi come errori. */ } if (n < 0) { /* n + 1 è sempre definito e - (n + 1) è sicuramente rappresentabile * tra gli interi positivi. */ un = ((unsigned) - (n + 1)) + 1; } for ( ; un >= ub; un /= ub) { } return (int) un; /* è compreso tra 0 e b con b > 1 e intero con segno, * per cui posso farlo.. */ }
Certamente non è particolarmente elegante, ma dovrebbe essere sempre ben definito e corretto. E' però necessario verificare che il valore restituito sia positivo.. non esattamente l'ideale. Forse qualcosa come le eccezioni o un assert personalizzato sarebbero preferibili.