J'ai récemment passé un test dans ma classe. L'un des problèmes était le suivant:
Étant donné un nombre n , écrivez une fonction en C / C ++ qui renvoie la somme des chiffres du nombre au carré . (Ce qui suit est important). La plage de n est [- (10 ^ 7), 10 ^ 7]. Exemple: si n = 123, votre fonction doit renvoyer 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).
Voici la fonction que j'ai écrite:
int sum_of_digits_squared(int n)
{
int s = 0, c;
while (n) {
c = n % 10;
s += (c * c);
n /= 10;
}
return s;
}
Il me semblait bien. Alors maintenant, le test est revenu et j'ai trouvé que le professeur ne m'a pas donné tous les points pour une raison que je ne comprends pas. Selon lui, pour que ma fonction soit complète, j'aurais dû ajouter le détail suivant:
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'argument en est que le nombre n est dans la plage [- (10 ^ 7), 10 ^ 7], il peut donc être un nombre négatif. Mais je ne vois pas où ma propre version de la fonction échoue. Si je comprends bien, la signification de while(n)
est while(n != 0)
, non while (n > 0)
, donc dans ma version de la fonction, le nombre n ne manquerait pas d'entrer dans la boucle. Cela fonctionnerait tout de même.
Ensuite, j'ai essayé les deux versions de la fonction sur mon ordinateur à la maison et j'ai obtenu exactement les mêmes réponses pour tous les exemples que j'ai essayés. Donc, sum_of_digits_squared(-123)
est égal à sum_of_digits_squared(123)
(qui, encore une fois, est égal à 14
) (même sans le détail que j'aurais dû ajouter apparemment). En effet, si j'essaie d'imprimer à l'écran les chiffres du nombre (du moins au plus important), dans le 123
cas où j'obtiens 3 2 1
et dans le -123
cas que j'obtiens -3 -2 -1
(ce qui est en fait assez intéressant). Mais dans ce problème, cela n'aurait pas d'importance puisque nous quadrillons les chiffres.
Alors, qui a tort?
EDIT : Mon mauvais, j'ai oublié de préciser et je ne savais pas que c'était important. La version de C utilisée dans notre classe et nos tests doit être C99 ou plus récente . Donc je suppose (en lisant les commentaires) que ma version obtiendrait la bonne réponse de quelque façon que ce soit.
n = n * (-1)
est une façon ridicule d'écriren = -n
; Seul un universitaire y penserait même. Sans parler d'ajouter les parenthèses redondantes.n = n * (-1)
? Wut ??? Ce que votre prof recherche est le suivant: `n = -n '. Le langage C a un opérateur moins unaire.Réponses:
Résumant une discussion qui a percolé dans les commentaires:
n == 0
. Lewhile(n)
test traitera parfaitement ce cas.%
opérandes négatifs a été défini différemment. Sur certains anciens systèmes (y compris, notamment, les premiers Unix sur un PDP-11, où Dennis Ritchie a initialement développé C), le résultat dea % b
était toujours dans la plage[0 .. b-1]
, ce qui signifie que -123% 10 était de 7. Sur un tel système, le test à l'avance pourn < 0
serait nécessaire.Mais la deuxième puce ne s'applique qu'aux temps antérieurs. Dans les versions actuelles des normes C et C ++, la division entière est définie pour tronquer vers 0, il s'avère donc que cela vous
n % 10
garantit le dernier chiffre (éventuellement négatif) den
même quandn
est négatif.Donc, la réponse à la question "Quel est le sens de
while(n)
?" est "Exactement le même quewhile(n != 0)
" et la réponse à "Ce code fonctionnera-t-il correctement aussi bien pour le négatif que pour le positifn
?" est "Oui, sous n'importe quel compilateur moderne conforme aux normes." La réponse à la question "Alors pourquoi l'instructeur l'a-t-il noté?" est probablement qu'ils ne sont pas au courant d'une redéfinition significative du langage qui est arrivée à C en 1999 et à C ++ en 2010 ou à peu près.la source
n == 0
au moins rend immédiatement et tout à fait évident pour tout lecteur ce qui se passe dans ce cas. Sans cela, le lecteur doit s'assurer que la boucle est bien sautée et que la valeur par défaut des
return est la bonne.n=0
. L'introduction de branches et de complications inutiles ne rend pas le code plus facile, il le rend plus difficile car maintenant vous devez non seulement montrer que l'algorithme général est correct, vous devez également penser séparément à tous les cas spéciaux.Votre code est parfaitement bien
Vous avez absolument raison et votre professeur a tort. Il n'y a absolument aucune raison d'ajouter cette complexité supplémentaire, car cela n'affecte pas du tout le résultat. Il introduit même un bug. (Voir ci-dessous)
Tout d'abord, la vérification séparée si
n
est zéro est évidemment complètement inutile et cela est très facile à réaliser. Pour être honnête, je remets en question la compétence de vos enseignants s'il a des objections à ce sujet. Mais je suppose que tout le monde peut avoir un pet de cerveau de temps en temps. Cependant, je pense que celawhile(n)
devrait être changé enwhile(n != 0)
car cela ajoute un peu de clarté supplémentaire sans même coûter une ligne supplémentaire. C'est une chose mineure cependant.Le second est un peu plus compréhensible, mais il a toujours tort.
Voici ce que dit la norme C11 6.5.5.p6 :
La note de bas de page dit ceci:
La troncature vers zéro signifie que la valeur absolue de
a/b
est égale à la valeur absolue de(-a)/b
pour tousa
etb
, ce qui signifie que votre code est parfaitement correct.Modulo est un calcul facile, mais peut être contre-intuitif
Cependant, votre professeur a un point que vous devez être prudent, car le fait que vous évaluez le résultat est en fait crucial ici. Calculer
a%b
selon la définition ci-dessus est un calcul facile, mais cela pourrait aller à l'encontre de votre intuition. Pour la multiplication et la division, le résultat est positif si les opérandes ont un signe égal. Mais quand il s'agit de modulo, le résultat a le même signe que le premier opérande. Le deuxième opérande n'affecte pas du tout le signe. Par exemple,7%3==1
mais(-7)%(-3)==(-1)
.Voici un extrait de démonstration:
Donc, ironiquement, votre professeur a prouvé son point de vue en se trompant.
Le code de votre professeur est défectueux
Oui, en fait. Si l'entrée est
INT_MIN
ET l'architecture est un complément à deux ET le modèle de bits où le bit de signe est 1 et tous les bits de valeur sont 0 n'est PAS une valeur d'interruption (l'utilisation du complément à deux sans valeurs d'interruption est très courante), alors le code de votre professeur donnera un comportement indéfini sur la lignen = n * (-1)
. Votre code est - même si légèrement - meilleur que le sien. Et compte tenu de l'introduction d'un petit bogue en rendant le code inutile et complexe et en obtenant une valeur absolument nulle, je dirais que votre code est BEAUCOUP mieux.En d'autres termes, dans les compilations où INT_MIN = -32768 (même si la fonction résultante ne peut pas recevoir une entrée <-32768 ou> 32767), l' entrée valide de -32768 provoque un comportement indéfini, car le résultat de - (- 32768i16) ne peut pas être exprimé comme un entier de 16 bits. (En fait, -32768 ne provoquerait probablement pas un résultat incorrect, car - (- 32768i16) est généralement évalué à -32768i16 et votre programme gère correctement les nombres négatifs.) (SHRT_MIN peut être -32768 ou -32767, selon le compilateur.)
Mais votre professeur a explicitement déclaré que cela
n
peut être dans la plage [-10 ^ 7; 10 ^ 7]. Un entier de 16 bits est trop petit; vous devez utiliser [au moins] un entier 32 bits. L'utilisationint
peut sembler sécuriser son code, sauf qu'ilint
ne s'agit pas nécessairement d'un entier 32 bits. Si vous compilez pour une architecture 16 bits, vos deux extraits de code sont défectueux. Mais votre code est encore bien meilleur car ce scénario réintroduit le bogue avecINT_MIN
mentionné ci-dessus avec sa version. Pour éviter cela, vous pouvez écrire à lalong
place deint
, qui est un entier 32 bits sur l'une ou l'autre architecture. Along
est garanti pour pouvoir contenir n'importe quelle valeur dans la plage [-2147483647; 2147483647]. La norme C11 5.2.4.2.1LONG_MIN
est souvent-2147483648
mais la valeur maximale (oui, maximale, c'est un nombre négatif) autorisée pourLONG_MIN
est2147483647
.Quelles modifications dois-je apporter à votre code?
Votre code est très bien tel qu'il est, donc ce ne sont pas vraiment des plaintes. C'est plus comme ça si j'ai vraiment, vraiment besoin de dire quelque chose sur votre code, il y a quelques petites choses qui pourraient le rendre un peu plus clair.
n
àn!=0
. Sémantiquement, c'est 100% équivalent, mais cela le rend un peu plus clair.c
(que j'ai renommédigit
) à l'intérieur de la boucle while car elle n'est utilisée que là-bas.long
vous assurer qu'il peut gérer l'ensemble des entrées.En fait, cela peut être un peu trompeur car - comme mentionné ci-dessus - la variable
digit
peut obtenir une valeur négative, mais un chiffre en soi n'est jamais ni positif ni négatif. Il y a plusieurs façons de contourner cela, mais c'est vraiment très intéressant, et je ne me soucierais pas de ces petits détails. En particulier, la fonction séparée pour le dernier chiffre va trop loin. Ironiquement, c'est l'une des choses que le code de vos enseignants résout réellement.sum += (digit * digit)
àsum += ((n%10)*(n%10))
la variable et ignorez-ladigit
complètement.digit
si négatif. Mais je déconseille fortement de rendre le code plus complexe juste pour donner un sens à un nom de variable. C'est une très forte odeur de code.int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Ceci est utile si vous souhaitez utiliser cette fonction ailleurs.c
comme vous le faites à l'origine. Ce nom de variable ne donne aucune information utile, mais d'un autre côté, il n'est pas trompeur non plus.Mais pour être honnête, à ce stade, vous devez passer à un travail plus important. :)
la source
INT_MIN
et que l'architecture utilise le complément à deux (ce qui est très courant), le code de votre enseignant produira un comportement indéfini. Aie. Cela laissera une marque. ;-)(a/b)*b + a%b ≡ a
, le code du PO dépend également du fait que l'/
arrondi vers zéro, et cela(-c)*(-c) ≡ c*c
. On pourrait faire valoir que les contrôles supplémentaires sont justifiés malgré une norme garantissant tout cela, car c'est suffisamment non évident. (Bien sûr, on pourrait tout aussi bien affirmer qu'il devrait plutôt y avoir un commentaire reliant les sections standard pertinentes, mais les directives de style varient.)%
et les/
opérateurs peuvent compiler en juste unidiv
sur x86, ousdiv
sur ARM ou autre chose. Pourtant, cela n'a rien à voir avec le code-gen plus rapide pour les diviseurs à constante de temps de compilation)Je n'aime pas complètement votre version ou votre professeur. La version de votre professeur fait les tests supplémentaires que vous signalez correctement comme inutiles. L'opérateur de mod de C n'est pas un mod mathématique approprié: un nombre négatif mod 10 produira un résultat négatif (le module mathématique correct est toujours non négatif). Mais puisque vous le quadrillez de toute façon, aucune différence.
Mais cela est loin d'être évident, donc j'ajouterais à votre code non pas les vérifications de votre professeur, mais un gros commentaire qui explique pourquoi cela fonctionne. Par exemple:
/ * REMARQUE: cela fonctionne pour les valeurs négatives, car le module devient carré * /
la source
%
sont mieux appelés un reste , car c'est le cas, même pour les types signés.-7 % 10
sera en fait-7
plutôt que 3.%
opérateur de C n'est pas.REMARQUE: Tandis que j'écrivais cette réponse, vous avez précisé que vous utilisez C. La majorité de ma réponse concerne C ++. Cependant, comme votre titre contient toujours C ++ et que la question est toujours étiquetée C ++, j'ai choisi de répondre de toute façon au cas où cela serait toujours utile à d'autres personnes, d'autant plus que la plupart des réponses que j'ai vues jusqu'à présent sont pour la plupart insatisfaisantes.
En C ++ moderne (Remarque: je ne sais pas vraiment où C en est), votre professeur semble avoir tort sur les deux plans.
La première est cette partie ici:
En C ++, c'est essentiellement la même chose que :
Cela signifie que votre temps équivaut à quelque chose comme ceci:
Cela signifie que puisque vous quittez simplement votre if si le moment ne s'exécutera pas de toute façon, il n'y a vraiment aucune raison de le mettre ici, car ce que vous faites après la boucle et dans le if sont de toute façon équivalents. Bien que je devrais dire que c'est pour une raison quelconque, ils étaient différents, vous auriez besoin de cela si.
Donc, vraiment, cette instruction if n'est pas particulièrement utile, sauf erreur de ma part.
La deuxième partie est l'endroit où les choses deviennent velues:
Le cœur du problème est de savoir ce que la sortie du module d'un nombre négatif produit.
En C ++ moderne, cela semble être généralement bien défini :
Et ensuite:
Comme l'a correctement montré l'affiche de la réponse citée, la partie importante de cette équation ici:
En prenant un exemple de votre cas, vous obtiendrez quelque chose comme ceci:
Le seul hic, c'est cette dernière ligne:
Cela signifie que dans un cas comme celui-ci, seul le signe semble être défini par l'implémentation. Cela ne devrait pas être un problème dans votre cas car, parce que vous évaluez cette valeur de toute façon.
Cela dit, gardez à l'esprit que cela ne s'applique pas nécessairement aux versions antérieures de C ++ ou C99. Si c'est ce que votre professeur utilise, cela pourrait être la raison.
EDIT: Non, je me trompe. Cela semble également être le cas pour C99 ou version ultérieure :
Et un autre endroit :
Donc voilà. Même en C99, cela ne semble pas vous affecter. L'équation est la même.
la source
(-1)%10
pourrait produire-1
ou1
; cela signifie qu'il pourrait produire-1
ou9
, et dans ce dernier cas,(-1)/10
il produira-1
et le code OP ne se terminera jamais.(a/b)*b + a%b == a
, puis laisseza=-1; b=10
, donner(-1/10)*10 + (-1)%10 == -1
. Maintenant, si en-1/10
effet est arrondi vers le bas (vers -inf), alors nous l'avons(-1/10)*10 == -10
, et vous devez avoir(-1)%10 == 9
pour que la première équation corresponde. Comme les autres réponses , ce n'est pas ainsi que cela fonctionne dans la ou les normes actuelles, mais c'est ainsi que cela fonctionnait. Il ne s'agit pas vraiment du signe du reste en tant que tel, mais de la façon dont la division est arrondie et de ce que le reste doit alors être pour satisfaire l'équation.(-1)*10+9=-1
, donc le choix(-1)/10=-1
et(-1)%10=9
ne viole pas l'équation gouvernante. D'un autre côté, le choix(-1)%10=1
ne peut pas satisfaire l'équation dominante, quelle que soit la façon dont il(-1)/10
est choisi; il n'y a aucun entierq
tel queq*10+1=-1
.Comme d'autres l'ont souligné, le traitement spécial pour n == 0 est un non-sens, car pour tout programmeur C sérieux, il est évident que "while (n)" fait le travail.
Le comportement pour n <0 n'est pas si évident, c'est pourquoi je préférerais voir ces 2 lignes de code:
ou au moins un commentaire:
Honnêtement, à quelle heure avez-vous commencé à considérer que n pourrait être négatif? Lors de l'écriture du code ou lors de la lecture des remarques de votre professeur?
la source
Cela me rappelle une mission que j'ai échoué
Retour dans les années 90. Le conférencier avait poussé sur les boucles et, pour faire court, notre mission était d'écrire une fonction qui retournerait le nombre de chiffres pour un entier donné> 0.
Ainsi, par exemple, le nombre de chiffres
321
serait3
.Bien que l'affectation ait simplement dit d'écrire une fonction qui renvoyait le nombre de chiffres, l'attente était que nous utiliserions une boucle qui divise par 10 jusqu'à ce que ... vous l'obteniez, comme couvert par la conférence .
Mais l'utilisation de boucles n'était pas explicite, donc I:
took the log, stripped away the decimals, added 1
et a ensuite été fustigée devant toute la classe.Le fait est que l'objectif de la mission était de tester notre compréhension de ce que nous avions appris pendant les cours . De la conférence que j'ai reçue, j'ai appris que le professeur d'informatique était un peu idiot (mais peut-être un idiot avec un plan?)
Dans votre situation:
J'aurais certainement fourni deux réponses:
la source
Généralement, dans les affectations, toutes les marques ne sont pas attribuées simplement parce que le code fonctionne. Vous obtenez également des notes pour rendre une solution facile à lire, efficace et élégante. Ces choses ne s'excluent pas toujours mutuellement.
Celui que je ne peux pas assez stries est "utiliser des noms de variables significatifs" .
Dans votre exemple, cela ne fait pas beaucoup de différence, mais si vous travaillez sur un projet avec un million de lignes de lisibilité du code devient très important.
Une autre chose que j'ai tendance à voir avec le code C, c'est que les gens essaient de paraître intelligents. Plutôt que d'utiliser while (n! = 0), je montrerai à tout le monde à quel point je suis intelligent en écrivant while (n) car cela signifie la même chose. Eh bien, c'est le cas dans le compilateur que vous avez, mais comme vous l'avez suggéré, l'ancienne version de votre professeur ne l'a pas implémentée de la même manière.
Un exemple courant consiste à référencer un index dans un tableau tout en l'incrémentant en même temps; Numbers [i ++] = iPrime;
Maintenant, le prochain programmeur qui travaille sur le code doit savoir si je suis incrémenté avant ou après l'affectation, juste pour que quelqu'un puisse se montrer.
Un mégaoctet d'espace disque est moins cher qu'un rouleau de papier toilette, optez pour la clarté plutôt que d'essayer d'économiser de l'espace, vos collègues programmeurs seront plus heureux.
la source
++i
incréments avant l'évaluation et lesi++
incréments après.while(n)
est également une fonction de langage commun. Basé sur une logique comme celle-ci, j'ai vu beaucoup de code commeif (foo == TRUE)
. Je suis d'accord sur les noms de variables, cependant.while(n)
n'est pas le pire exemple pour ça (j'aime leif(strcmp(one, two))
plus)i++
et++i
modifier le code C qui devrait être utilisé en production.Je ne dirais pas si la définition originale ou moderne de '%' est meilleure, mais quiconque écrit deux déclarations de retour dans une fonction aussi courte ne devrait pas du tout enseigner la programmation C. Extra return est une instruction goto et nous n'utilisons pas goto en C. De plus, le code sans la vérification zéro aurait le même résultat, le retour supplémentaire en a rendu la lecture plus difficile.
la source
int findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
L'énoncé du problème prête à confusion, mais l'exemple numérique clarifie la signification de la somme des chiffres du nombre au carré . Voici une version améliorée:
La fonction que vous avez écrite est correcte sauf pour 2 détails:
long
pour tenir compte de toutes les valeurs dans la plage spécifiée car le typelong
est garanti par la norme C d'avoir au moins 31 bits de valeur, donc une plage suffisante pour représenter toutes les valeurs dans [-10 7 , 10 7 ] . (Notez que le typeint
est suffisant pour le type de retour, dont la valeur maximale est568
.)%
opérandes négatifs n'est pas intuitif et ses spécifications variaient entre la norme C99 et les éditions précédentes. Vous devez documenter pourquoi votre approche est valable même pour les entrées négatives.Voici une version modifiée:
La réponse de l'enseignant a plusieurs défauts:
int
peut avoir une plage de valeurs insuffisante.0
.n = INT_MIN
.Étant donné les contraintes supplémentaires dans l'énoncé du problème (C99 et plage de valeurs pour
n
), seul le premier défaut est un problème. Le code supplémentaire produit toujours les bonnes réponses.Vous devriez obtenir une bonne note dans ce test, mais l'explication est requise dans un test écrit pour montrer votre compréhension des problèmes négatifs
n
, sinon l'enseignant peut supposer que vous n'étiez pas au courant et que vous avez juste eu de la chance. Lors d'un examen oral, vous auriez obtenu une question et votre réponse l'aurait clouée.la source