Devo gestire esplicitamente numeri negativi o zero quando si sommano le cifre quadrate?


220

Di recente ho fatto un test nella mia classe. Uno dei problemi era il seguente:

Dato un numero n , scrivere una funzione in C / C ++ che restituisce la somma delle cifre del numero al quadrato . (Quanto segue è importante). L' intervallo di n è [- (10 ^ 7), 10 ^ 7]. Esempio: se n = 123, la funzione dovrebbe restituire 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Questa è la funzione che ho scritto:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Mi è sembrato giusto. Quindi ora il test è tornato e ho scoperto che l'insegnante non mi ha dato tutti i punti per un motivo che non capisco. Secondo lui, per completare la mia funzione, avrei dovuto aggiungere i seguenti dettagli:

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

L'argomento è che il numero n è compreso nell'intervallo [- (10 ^ 7), 10 ^ 7], quindi può essere un numero negativo. Ma non vedo dove la mia versione della funzione non riesce. Se capisco correttamente, il significato di while(n)è while(n != 0), no while (n > 0) , quindi nella mia versione della funzione il numero n non riuscirebbe ad entrare nel ciclo. Funzionerebbe allo stesso modo.

Quindi, ho provato entrambe le versioni della funzione sul mio computer a casa e ho ottenuto esattamente le stesse risposte per tutti gli esempi che ho provato. Così,sum_of_digits_squared(-123) è uguale a sum_of_digits_squared(123)(che di nuovo è uguale a 14) (anche senza i dettagli che apparentemente avrei dovuto aggiungere). In effetti, se provo a stampare sullo schermo le cifre del numero (dalla meno alla più grande importanza), nel 123caso in cui ottengo 3 2 1e nel -123caso in cui ottengo -3 -2 -1(che in realtà è piuttosto interessante). Ma in questo problema non importerebbe poiché quadriamo le cifre.

Quindi, chi ha torto?

MODIFICARE : Mio male, ho dimenticato di specificare e non sapevo che fosse importante. La versione di C utilizzata nella nostra classe e nei test deve essere C99 o più recente . Quindi immagino (leggendo i commenti) che la mia versione otterrebbe la risposta corretta in qualsiasi modo.


120
n = n * (-1)è un modo ridicolo di scrivere n = -n; Solo un accademico potrebbe persino pensarci. Per non parlare dell'aggiunta delle parentesi ridondanti.
user207421

32
Scrivi una serie di test unitari per verificare se una determinata implementazione si adatta alle specifiche. Se c'è un problema (funzionale) con un pezzo di codice, dovrebbe essere possibile scrivere un test che dimostri il risultato errato dato un input particolare.
Carl,

94
Trovo interessante che "la somma delle cifre del numero al quadrato" possa essere interpretata in tre (3) modi completamente diversi. (Se il numero è 123, le possibili interpretazioni producono 18, 14 e 36.)
Andreas Rejbrand

23
@ilkkachu: "la somma delle cifre del numero al quadrato". Bene, "il numero al quadrato" è chiaramente 123 ^ 2 = 15129, quindi "la somma delle cifre del numero al quadrato" è "la somma delle cifre di 15129", che ovviamente è 1 + 5 + 1 + 2 + 9 = 18.
Andreas Rejbrand,

15
n = n * (-1)? Wut ??? Ciò che il tuo prof sta cercando è questo: `n = -n '. Il linguaggio C ha un operatore meno unario.
Kaz,

Risposte:


245

Riassumendo una discussione che è stata percolata nei commenti:

  • Non ci sono buoni motivi per provare in anticipo n == 0. Il while(n)test gestirà perfettamente quel caso.
  • È probabile che il tuo insegnante sia ancora abituato ai tempi precedenti, quando il risultato di %operandi negativi era stato definito in modo diverso. Su alcuni vecchi sistemi (incluso, in particolare, i primi Unix su un PDP-11, in cui Dennis Ritchie originariamente sviluppava C), il risultato a % bera sempre nell'intervallo [0 .. b-1], il che significa che -123% 10 era 7. Su un tale sistema, il test in anticipo per n < 0sarebbe necessario.

Ma il secondo proiettile si applica solo ai tempi precedenti. Nelle attuali versioni di entrambi gli standard C e C ++, la divisione di interi è definita per troncare verso 0, quindi risulta che n % 10è garantito che fornisca l'ultima (possibilmente negativa) cifra nanche quando nè negativa.

Quindi la risposta alla domanda "Qual è il significato di while(n)?" è "Esattamente lo stesso di while(n != 0)" e la risposta a "Questo codice funzionerà correttamente sia in negativo che in positivo n?" è "Sì, in qualsiasi compilatore moderno conforme agli standard". La risposta alla domanda "Allora perché l'istruttore l'ha annotato?" probabilmente non sono a conoscenza di una significativa ridefinizione del linguaggio avvenuta in C nel 1999 e in C ++ nel 2010 o giù di lì.


39
"Non c'è motivo di provare in anticipo per n == 0" - tecnicamente, è corretto. Ma dato che stiamo parlando di un professore in un ambiente di insegnamento, possono apprezzare la chiarezza sulla brevità molto più di noi. L'aggiunta del test aggiuntivo per n == 0almeno lo rende immediatamente e assolutamente ovvio per qualsiasi lettore cosa succede in quel caso. Senza di esso, il lettore deve assicurarsi che il loop sia effettivamente saltato e che il valore predefinito di sreturn sia quello corretto.
ilkkachu,

22
Inoltre, il professore potrebbe voler sapere che lo studente è consapevole e ha pensato al perché e al comportamento della funzione con un input pari a zero (ovvero che non restituisce il valore corretto per errore ). Potrebbero aver incontrato studenti che non si rendono conto di cosa potrebbe accadere in quel caso, che il ciclo può essere eseguito zero volte, ecc. Quando hai a che fare con un'impostazione di insegnamento, è meglio essere più chiari su eventuali ipotesi e casi d'angolo. ..
ilkkachu,

36
@ilkkachu In tal caso, l'insegnante dovrebbe distribuire un'attività che richiede tale test per funzionare correttamente.
klutt,

38
@ilkkachu Bene, prendo in considerazione il caso generale, perché apprezzo assolutamente la chiarezza sulla brevità - e praticamente sempre, non necessariamente solo in un contesto pedagogico. Ma detto questo, a volte brevità è la chiarezza, e se si può organizzare per il codice principale per gestire sia il caso generale e il caso limite (s), senza ingombrare il codice (e l'analisi di copertura) con casi particolari per i casi limite , è una cosa bellissima! Penso che sia qualcosa da apprezzare anche a livello di principiante.
Steve Summit

49
@ilkkachu Con questo argomento dovresti sicuramente aggiungere anche i test per n = 1 e così via. Non c'è niente di speciale n=0. L'introduzione di rami e complicazioni non necessari non semplifica il codice, lo rende più difficile poiché ora non devi solo dimostrare che l'algoritmo generale è corretto, ma devi anche pensare a tutti i casi speciali separatamente.
Voo

107

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)/bper tutto aeb , 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%bsecondo 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==1ma(-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_MINAND 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 npuò 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 intpotrebbe sembrare sicuro per il suo codice, tranne per il fatto che intnon è 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_MINmenzionato sopra con la sua versione. Per evitare ciò, è possibile scrivere longinvece 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-2147483648ma 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 na 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 longper 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 digitpuò 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 digitcompletamente la variabile .
  • Cambia il segno di digitse 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 ccome 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. :)


19
Se l'input è INT_MINe l'architettura utilizza il complemento a due (che è molto comune), il codice dell'insegnante produrrà un comportamento indefinito. Ahia. Questo lascerà un segno. ;-)
Andrew Henle,

5
Va detto che, oltre a (a/b)*b + a%b ≡ a, il codice del PO dipende anche dal fatto che si /arrotonda verso zero e che (-c)*(-c) ≡ c*c. Si potrebbe sostenere che i controlli extra sono giustificati nonostante lo standard garantisca tutto ciò, poiché è sufficientemente non ovvio. (Ovviamente si potrebbe anche sostenere che dovrebbe esserci piuttosto un commento che collega le sezioni standard pertinenti, ma le linee guida di stile variano.)
leftaroundabout

7
@MartinRosenau Dici "potrebbe". Sei sicuro che questo stia realmente accadendo o che sia consentito dallo standard o qualcosa del genere o stai solo ipotizzando?
Klutt

6
@MartinRosenau: Ok, ma l'uso di questi switch non lo renderebbe più C. GCC / clang non ha alcun interruttore che interrompe la divisione dei numeri interi su qualsiasi ISA di cui sono a conoscenza. Anche se ignorare il bit di segno potrebbe forse dare uno speedup usando il normale inverso moltiplicativo per divisori costanti. (Ma tutti gli ISA che conosco hanno un'istruzione di divisione hardware che lo implementa nel modo C99, troncando verso zero, quindi C %e gli /operatori possono compilare solo idivsu un x86, o sdivsu ARM o qualsiasi altra cosa. Tuttavia, questo non è correlato al molto code-gen più veloce per i divisori costanti di compilazione)
Peter Cordes,

5
@TonyK AFIK, è così che di solito viene risolto, ma secondo lo standard è UB.
klutt,

20

Non mi piace completamente né la tua versione né il tuo insegnante. La versione del tuo insegnante non è necessaria per eseguire ulteriori test che hai correttamente sottolineato. L'operatore mod di C non è un mod matematico appropriato: un numero negativo mod 10 produrrà un risultato negativo (il modulo matematico corretto è sempre non negativo). Ma dal momento che lo stai quadrando comunque, nessuna differenza.

Ma questo è tutt'altro che ovvio, quindi aggiungerei al tuo codice non i controlli del tuo insegnante, ma un grande commento che spiega perché funziona. Per esempio:

/ * NOTA: funziona con valori negativi, poiché il modulo viene quadrato * /


9
C %è meglio chiamato un resto , perché è quello, anche per i tipi firmati.
Peter Cordes,

15
La quadratura è importante, ma penso che sia la parte ovvia. Ciò che dovrebbe essere sottolineato è che (ad esempio) -7 % 10 sarà effettivamente-7 piuttosto che 3.
Jacob Raihle

5
"Modulo matematico adeguato" non significa nulla. Il termine corretto è "modulo euclideo" (attenzione al suffisso!) Ed è proprio ciò che l' %operatore di C non è.
Jan Hudec,

Mi piace questa risposta perché risolve la questione di molteplici modi di interpretare il modulo. Non lasciare mai una cosa del genere al caso / interpretazione. Questo non è un codice golf.
Harper - Ripristina Monica il

1
"il corretto modulo matematico è sempre non negativo" - Non proprio. Il risultato di un'operazione modulo è una classe di equivalenza , ma è comune considerare il risultato come il numero non negativo più piccolo appartenente a quella classe.
klutt,

10

NOTA: Mentre stavo scrivendo questa risposta, hai chiarito che stai usando C. La maggior parte della mia risposta riguarda C ++. Tuttavia, poiché il tuo titolo ha ancora C ++ e la domanda è ancora taggata C ++, ho scelto di rispondere comunque nel caso in cui ciò sia ancora utile ad altre persone, soprattutto perché la maggior parte delle risposte che ho visto fino ad ora sono per lo più insoddisfacenti.

Nel moderno C ++ (Nota: non so davvero dove si trovi la C in questo caso), il tuo professore sembra avere torto su entrambi i fronti.

La prima è questa parte qui:

if (n == 0) {
        return 0;
}

In C ++, questo è fondamentalmente la stessa cosa di :

if (!n) {
        return 0;
}

Ciò significa che il tuo tempo equivale a qualcosa del genere:

while(n != 0) {
    // some implementation
}

Ciò significa che dal momento che stai semplicemente uscendo dal tuo if quando il tempo non si eseguirà comunque, non c'è davvero motivo di metterlo se qui, dal momento che quello che stai facendo dopo il ciclo e in if sono comunque equivalenti. Anche se dovrei dire che per qualche ragione erano diversi, avresti bisogno di questo se.

Quindi davvero, questa istruzione if non è particolarmente utile se non sbaglio.

La seconda parte è dove le cose diventano pelose:

if (n < 0) {
    n = n * (-1);
}  

Il cuore del problema è quello che produce l'output del modulo di un numero negativo.

Nel C ++ moderno, questo sembra essere per lo più ben definito :

Il binario / operatore produce il quoziente e l'operatore binario% produce il resto dalla divisione della prima espressione per la seconda. Se il secondo operando di / o% è zero, il comportamento non è definito. Per gli operandi integrali l'operatore / fornisce il quoziente algebrico con qualsiasi parte frazionaria scartata; se il quoziente a / b è rappresentabile nel tipo di risultato, (a / b) * b + a% b è uguale a a.

E più tardi:

Se entrambi gli operandi non sono negativi, il resto non è negativo; in caso contrario, il segno del resto è definito dall'implementazione.

Come sottolinea correttamente il poster della risposta citata, la parte importante di questa equazione proprio qui:

(a / b) * b + a% b

Prendendo un esempio del tuo caso, otterrai qualcosa del genere:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

L'unica cattura è l'ultima riga:

Se entrambi gli operandi non sono negativi, il resto non è negativo; in caso contrario, il segno del resto è definito dall'implementazione.

Ciò significa che in un caso come questo, solo il segno sembra essere definito dall'implementazione. Questo non dovrebbe essere un problema nel tuo caso perché, perché stai comunque quadrando questo valore.

Detto questo, tieni presente che questo non si applica necessariamente alle versioni precedenti di C ++ o C99. Se è quello che sta usando il tuo professore, potrebbe essere il motivo.


EDIT: No, mi sbaglio. Questo sembra essere il caso anche per C99 o versioni successive :

C99 richiede che quando a / b è rappresentabile:

(a / b) * b + a% b deve essere uguale a

E un altro posto :

Quando gli interi sono divisi e la divisione è inesatta, se entrambi gli operandi sono positivi, il risultato dell'operatore / è il numero intero più grande inferiore al quoziente algebrico e il risultato dell'operatore% è positivo. Se uno degli operandi è negativo, se il risultato dell'operatore / è il numero intero più grande inferiore al quoziente algebrico o il numero intero più piccolo maggiore del quoziente algebrico è definito dall'implementazione, così come il segno del risultato dell'operatore%. Se il quoziente a / b è rappresentabile, l'espressione (a / b) * b + a% b deve essere uguale a.

ANSI C o ISO C specificano quale dovrebbe essere -5% 10?

Quindi si Anche in C99, questo non sembra influenzarti. L'equazione è la stessa.


1
Le parti che hai citato non supportano questa risposta. "il segno del resto è definito dall'implementazione" non significa che (-1)%10potrebbe produrre -1o 1; significa che potrebbe produrre -1o 9, e in quest'ultimo caso (-1)/10produrrà -1e il codice di OP non si chiuderà mai.
Stewbasic,

Potresti indicare una fonte per questo? Ho difficoltà a credere che (-1) / 10 sia -1. Dovrebbe essere 0. Inoltre, (-1)% 10 = 9 sembra violare l'equazione di governo.
Chipster

1
@Chipster, inizia con (a/b)*b + a%b == a, poi lascia a=-1; b=10, dare (-1/10)*10 + (-1)%10 == -1. Ora, se -1/10davvero viene arrotondato per difetto (verso -inf), allora abbiamo (-1/10)*10 == -10, ed è necessario che (-1)%10 == 9la prima equazione corrisponda. Come le altre risposte affermano, questo non è come funziona all'interno degli standard attuali, ma è come funzionava. Non si tratta davvero del segno del resto in quanto tale, ma di come la divisione viene arrotondata e quale deve essere il resto per soddisfare l'equazione.
ilkkachu,

1
@Chipster La fonte sono gli snippet che hai citato. Si noti che (-1)*10+9=-1, quindi, la scelta (-1)/10=-1e (-1)%10=9non viola l'equazione di governo. D'altra parte la scelta (-1)%10=1non può soddisfare l'equazione di governo, indipendentemente da come (-1)/10viene scelta; non esiste un numero intero qtale q*10+1=-1.
Stewbasic,

8

Come altri hanno sottolineato, il trattamento speciale per n == 0 è senza senso, dal momento che per ogni programmatore C serio è ovvio che "while (n)" fa il lavoro.

Il comportamento per n <0 non è così ovvio, ecco perché preferirei vedere quelle 2 righe di codice:

if (n < 0) 
    n = -n;

o almeno un commento:

// don't worry, works for n < 0 as well

Onestamente, a che ora hai iniziato a considerare che n potrebbe essere negativo? Quando scrivi il codice o quando leggi le osservazioni del tuo insegnante?


1
Indipendentemente dal fatto che N sia negativo, N al quadrato sarà positivo. Quindi, perché rimuovere il segno in primo luogo? -3 * -3 = 9; 3 * 3 = 9. O la matematica è cambiata negli ultimi 30 anni da quando l'ho imparato?
Merovex,

2
@CB Per essere onesti, non ho nemmeno notato che n potrebbe essere negativo mentre stavo scrivendo il test, ma quando è tornato ho avuto la sensazione che il ciclo while non sarebbe stato saltato, anche se il numero fosse negativo. Ho fatto alcuni test sul mio computer e questo ha confermato il mio scetticismo. Successivamente, ho pubblicato questa domanda. Quindi no, non stavo pensando così profondamente mentre scrivevo il codice.
user010517720

5

Questo mi ricorda un incarico che ho fallito

Nel lontano 90. Il docente stava spuntando dei cicli e, per farla breve, il nostro compito era quello di scrivere una funzione che restituisse il numero di cifre per ogni dato intero> 0.

Quindi, ad esempio, il numero di cifre in 321sarebbe 3.

Sebbene il compito semplicemente dicesse di scrivere una funzione che restituiva il numero di cifre, l'aspettativa era che avremmo usato un ciclo che si divide per 10 fino a quando ... lo capisci, come spiegato nella lezione .

Ma usare i loop non è stato esplicitamente dichiarato così io: took the log, stripped away the decimals, added 1ed è stato successivamente agitato davanti a tutta la classe.

Il punto è che lo scopo del compito era quello di testare la nostra comprensione di ciò che avevamo imparato durante le lezioni . Dalla lezione che ho ricevuto ho appreso che l'insegnante di computer era un po 'un idiota (ma forse un idiota con un piano?)


Nella tua situazione:

scrivere una funzione in C / C ++ che restituisce la somma delle cifre del numero al quadrato

Avrei sicuramente fornito due risposte:

  • la risposta corretta (quadrando prima il numero) e
  • la risposta errata in linea con l'esempio, giusto per renderlo felice ;-)

5
E anche un terzo a quadrare la somma delle cifre?
Kriss,

@kriss - sì, non sono così intelligente :-(
SlowLearner,

1
Ho anche avuto la mia parte di assegnazioni troppo vaghe nel mio tempo da studente. Un insegnante voleva una libreria di manipolazione, ma fu sorpreso dal mio codice e disse che non funzionava. Ho dovuto fargli notare che non ha mai definito l'endianness nel suo incarico e ha scelto il bit più basso come bit 0 mentre ho fatto l'altra scelta. L'unica parte fastidiosa è che avrebbe dovuto essere in grado di capire dove fosse la differenza senza che glielo dicessi.
Kriss,

1

Generalmente nelle assegnazioni non tutti i voti vengono assegnati solo perché il codice funziona. Ottieni anche voti per rendere una soluzione di facile lettura, efficiente ed elegante. Queste cose non si escludono sempre a vicenda.

Uno che non riesco abbastanza è "usa nomi di variabili significative" .

Nel tuo esempio non fa molta differenza, ma se stai lavorando a un progetto con milioni di righe di leggibilità del codice diventa molto importante.

Un'altra cosa che tendo a vedere con il codice C sono le persone che cercano di sembrare intelligenti. Invece di usare while (n! = 0) mostrerò a tutti quanto sono intelligente scrivendo while (n) perché significa la stessa cosa. Bene lo fa nel compilatore che hai ma, come hai suggerito, la versione precedente dell'insegnante non l'ha implementata allo stesso modo.

Un esempio comune è fare riferimento a un indice in un array incrementandolo allo stesso tempo; Numbers [i ++] = iPrime;

Ora, il prossimo programmatore che lavora sul codice deve sapere se vengo incrementato prima o dopo l'assegnazione, solo così qualcuno potrebbe mettersi in mostra.

Un megabyte di spazio su disco è più economico di un rotolo di carta igienica, per maggiore chiarezza anziché cercare di risparmiare spazio, i tuoi colleghi programmatori saranno più felici.


2
Ho programmato in C solo una manciata di volte e so che ++iincrementi prima della valutazione e i++incrementi dopo. while(n)è anche una caratteristica del linguaggio comune. Basato su una logica come questa ho visto un sacco di codice come if (foo == TRUE). Sono d'accordo su: nomi variabili, però.
alan ocallaghan,

3
Generalmente non è un cattivo consiglio, ma evitare le funzioni linguistiche di base (che le persone inevitabilmente incontreranno comunque) ai fini della programmazione difensiva è eccessivo. Il codice breve e chiaro è spesso più leggibile comunque. Qui non stiamo parlando di pazzeschi perl o bash, ma solo di funzioni linguistiche di base.
alan ocallaghan,

1
Non sono sicuro di cosa siano stati dati i voti negativi di questa risposta. Per me tutto ciò che è dichiarato qui è vero e importante e un buon consiglio per ogni sviluppatore. Soprattutto la parte di programmazione intelligente, anche se while(n)non è l'esempio peggiore per questo ("mi piace" di if(strcmp(one, two))più)
Kai Huppmann,

1
Inoltre, non dovresti davvero permettere a nessuno che non conosca la differenza tra i++e ++imodificare il codice C che dovrebbe essere usato in produzione.
Klutt

2
@PaulMcCarthy Siamo entrambi per la leggibilità del codice. Ma non siamo d'accordo su cosa significhi. Inoltre, non è obiettivo. Ciò che è facile da leggere per una persona può essere difficile per un'altra. La conoscenza della personalità e del background influenza fortemente questo. Il mio punto principale è che non ottieni la massima leggibilità seguendo ciecamente alcune regole.
Klut,

0

Non direi se la definizione originale o moderna di '%' sia migliore, ma chiunque scriva due dichiarazioni di ritorno in una funzione così breve non dovrebbe insegnare affatto alla programmazione C. Il ritorno extra è un'istruzione goto e non usiamo goto in C. Inoltre il codice senza il controllo zero avrebbe lo stesso risultato, il ritorno extra rendeva più difficile la lettura.


4
"Il ritorno extra è un'istruzione goto e non usiamo goto in C." - è una combinazione di una generalizzazione molto ampia e un tratto molto lontano.
SS Anne,

1
"Noi" sicuramente usiamo goto in C. Non c'è niente di sbagliato in questo.
klutt,

1
Inoltre, non c'è niente di sbagliato in una funzione comeint findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt,

1
@PeterKrassoi Non sto sostenendo codice difficile da leggere, ma ho visto tonnellate di esempi in cui il codice sembra un disastro completo solo per evitare un semplice goto o una dichiarazione di ritorno extra. Ecco un po 'di codice con l'uso appropriato di goto. Ti sfido a rimuovere le dichiarazioni goto mentre allo stesso tempo faciliti la lettura e la
manutenzione

1
@PeterKrassoi Mostra anche la tua versione di questo: pastebin.com/qBmR78KA
klutt

0

L'affermazione del problema è confusa, ma l'esempio numerico chiarisce il significato della somma delle cifre del numero al quadrato . Ecco una versione migliorata:

Scrivi una funzione nel sottoinsieme comune di C e C ++ che accetta un numero intero nnell'intervallo [-10 7 , 10 7 ] e restituisce la somma dei quadrati delle cifre della sua rappresentazione in base 10. Esempio: se nè 123, la tua funzione dovrebbe tornare 14(1 2 + 2 2 + 3 2 = 14).

La funzione che hai scritto va bene tranne 2 dettagli:

  • L'argomento dovrebbe avere il tipo longper adattarsi a tutti i valori nell'intervallo specificato poiché il tipo longè garantito dallo standard C per avere almeno 31 bit di valore, quindi un intervallo sufficiente per rappresentare tutti i valori in [-10 7 , 10 7 ] . (Si noti che il tipo intè sufficiente per il tipo restituito, il cui valore massimo è 568.)
  • Il comportamento di %per operandi negativi non è intuitivo e le sue specifiche variano tra lo standard C99 e le precedenti edizioni. Dovresti documentare perché il tuo approccio è valido anche per input negativi.

Ecco una versione modificata:

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

La risposta dell'insegnante ha molteplici difetti:

  • il tipo intpuò avere un intervallo di valori insufficiente.
  • non è necessario un caso speciale il valore 0.
  • negare valori negativi non è necessario e potrebbe avere un comportamento indefinito per n = INT_MIN.

Dati i vincoli aggiuntivi nell'istruzione del problema (C99 e intervallo di valori per n), solo il primo difetto è un problema. Il codice aggiuntivo produce ancora le risposte corrette.

Dovresti ottenere un buon voto in questo test, ma la spiegazione è richiesta in un test scritto per mostrare la tua comprensione dei problemi per il negativo n, altrimenti l'insegnante potrebbe presumere che tu non fossi consapevole e sei stato fortunato. In una prova orale, avresti ricevuto una domanda e la tua risposta l'avrebbe inchiodata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.