Il tuo codice è perfetto
Hai assolutamente ragione e il tuo insegnante ha torto. Non c'è assolutamente alcun motivo per aggiungere quella complessità in più, poiché non influisce affatto sul risultato. Presenta persino un bug. (Vedi sotto)
Innanzitutto, il controllo separato se n
è zero è ovviamente completamente inutile e questo è molto facile da realizzare. Ad essere sincero, in realtà metto in dubbio la competenza dei tuoi insegnanti se ha obiezioni in merito. Ma credo che tutti possano avere una scoreggia cerebrale di volta in volta. Tuttavia, penso che while(n)
dovrebbe essere cambiato inwhile(n != 0)
perché aggiunge un po 'di chiarezza in più senza nemmeno costare una linea in più. È una cosa minore però.
Il secondo è un po 'più comprensibile, ma ha ancora torto.
Questo è ciò che dice lo standard C11 6.5.5.p6 :
Se il quoziente a / b è rappresentabile, l'espressione (a / b) * b + a% b deve essere uguale a a; in caso contrario, il comportamento di a / b e a% b non è definito.
La nota a piè di pagina dice questo:
Questo è spesso chiamato "troncamento verso zero".
Il troncamento verso zero indica che il valore assoluto per a/b
è uguale al valore assoluto (-a)/b
per tutto a
eb
, a sua volta significa che il tuo codice è perfettamente a posto.
Modulo è semplice matematica, ma può essere controintuitivo
Tuttavia, il tuo insegnante ha un punto in cui dovresti stare attento, perché il fatto che stai quadrando il risultato è in realtà cruciale qui. Calcolare a%b
secondo la definizione di cui sopra è semplice matematica, ma potrebbe andare contro la tua intuizione. Per moltiplicazione e divisione, il risultato è positivo se gli operandi hanno lo stesso segno. Ma quando si tratta di modulo, il risultato ha lo stesso segno del primo operando. Il secondo operando non influisce affatto sul segno. Ad esempio, 7%3==1
ma(-7)%(-3)==(-1)
.
Ecco uno snippet che lo dimostra:
$ cat > main.c
#include <stdio.h>
void f(int a, int b)
{
printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}
int main(void)
{
int a=7, b=3;
f(a,b);
f(-a,b);
f(a,-b);
f(-a,-b);
}
$ gcc main.c -Wall -Wextra -pedantic -std=c99
$ ./a.out
a: 7 b: 3 a/b: 2 a%b: 1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: -7 b: 3 a/b: -2 a%b: -1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: 7 b: -3 a/b: -2 a%b: 1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: -7 b: -3 a/b: 2 a%b: -1 (a%b)^2: 1 (a/b)*b+a%b==a: true
Quindi, ironia della sorte, il tuo insegnante ha dimostrato il suo punto di vista sbagliando.
Il codice del tuo insegnante è difettoso
Sì, lo è davvero. Se l'input è INT_MIN
AND l'architettura è il complemento a due E il modello di bit in cui il bit del segno è 1 e tutti i bit di valore sono 0 NON è un valore trap (l'utilizzo del complemento a due senza valori trap è molto comune) quindi il codice dell'insegnante produrrà un comportamento indefinito sulla linea n = n * (-1)
. Il tuo codice è - se mai così leggermente - migliore del suo. E considerando l'introduzione di un piccolo bug rendendo il codice inutilmente complesso e ottenendo un valore assolutamente zero, direi che il tuo codice è MOLTO migliore.
In altre parole, nelle compilazioni in cui INT_MIN = -32768 (anche se la funzione risultante non può ricevere un input <-32768 o> 32767), il valido input di -32768 provoca un comportamento indefinito, poiché il risultato di - (- 32768i16) non può essere espresso come un numero intero a 16 bit. (In realtà, -32768 probabilmente non causerebbe un risultato errato, perché - (- 32768i16) in genere valuta -32768i16 e il programma gestisce correttamente i numeri negativi.) (SHRT_MIN potrebbe essere -32768 o -32767, a seconda del compilatore.)
Ma il tuo insegnante ha dichiarato esplicitamente che n
può essere compreso nell'intervallo [-10 ^ 7; 10 ^ 7]. Un numero intero a 16 bit è troppo piccolo; dovresti usare [almeno] un numero intero a 32 bit. L'uso int
potrebbe sembrare sicuro per il suo codice, tranne per il fatto che int
non è necessariamente un numero intero a 32 bit. Se si compila per un'architettura a 16 bit, entrambi i frammenti di codice sono difettosi. Ma il tuo codice è ancora molto meglio perché questo scenario reintroduce il bug INT_MIN
menzionato sopra con la sua versione. Per evitare ciò, è possibile scrivere long
invece di int
, che è un numero intero a 32 bit su entrambe le architetture. A long
è garantito per essere in grado di contenere qualsiasi valore nell'intervallo [-2147483647; 2147483647]. Lo standard C11 5.2.4.2.1 LONG_MIN
è spesso-2147483648
ma il valore massimo consentito (sì, massimo, è un numero negativo) LONG_MIN
è 2147483647
.
Quali modifiche dovrei apportare al tuo codice?
Il tuo codice va bene così com'è, quindi questi non sono davvero reclami. È più simile che se davvero, davvero dovessi dire qualcosa sul tuo codice, ci sono alcune piccole cose che potrebbero renderlo un po 'più chiaro.
- I nomi delle variabili potrebbero essere leggermente migliori, ma è una funzione breve che è facile da capire, quindi non è un grosso problema.
- È possibile modificare la condizione da
n
a n!=0
. Semanticamente, è equivalente al 100%, ma lo rende un po 'più chiaro.
- Sposta la dichiarazione di
c
(che ho ribattezzato digit
) all'interno del ciclo while poiché è usata solo lì.
- Modificare il tipo di argomento
long
per assicurarsi che sia in grado di gestire l'intero set di input.
int sum_of_digits_squared(long n)
{
long sum = 0;
while (n != 0) {
int digit = n % 10;
sum += (digit * digit);
n /= 10;
}
return sum;
}
In realtà, questo può essere un po 'fuorviante perché - come detto sopra - la variabile digit
può ottenere un valore negativo, ma una cifra in sé non è mai né positiva né negativa. Ci sono alcuni modi per aggirare questo, ma questo è DAVVERO nitido, e non mi interesserei dettagli così piccoli. Soprattutto la funzione separata per l'ultima cifra la sta spingendo troppo oltre. Ironia della sorte, questa è una delle cose che il codice degli insegnanti risolve effettivamente.
- Passare
sum += (digit * digit)
a sum += ((n%10)*(n%10))
e saltare digit
completamente la variabile .
- Cambia il segno di
digit
se negativo. Ma sconsiglio vivamente di rendere il codice più complesso solo per dare un senso al nome di una variabile. È un odore di codice MOLTO forte.
- Crea una funzione separata che estrae l'ultima cifra.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Ciò è utile se si desidera utilizzare quella funzione altrove.
- Basta nominarlo
c
come in origine. Quel nome di variabile non fornisce alcuna informazione utile, ma d'altra parte non è neppure fuorviante.
Ma ad essere sinceri, a questo punto dovresti passare a lavori più importanti. :)
n = n * (-1)
è un modo ridicolo di scriveren = -n
; Solo un accademico potrebbe persino pensarci. Per non parlare dell'aggiunta delle parentesi ridondanti.