Dois-je gérer explicitement les nombres négatifs ou zéro lors de la sommation des chiffres au carré?


220

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 123cas où j'obtiens 3 2 1et dans le -123cas 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.


120
n = n * (-1)est une façon ridicule d'écrire n = -n; Seul un universitaire y penserait même. Sans parler d'ajouter les parenthèses redondantes.
user207421

32
Écrivez une série de tests unitaires pour vérifier si une implémentation donnée correspond à la spécification. S'il y a un problème (fonctionnel) avec un morceau de code, il devrait être possible d'écrire un test qui démontre le résultat incorrect avec une entrée particulière.
Carl

94
Je trouve intéressant que "la somme des chiffres du nombre au carré" puisse être interprétée de trois (3) manières complètement différentes. (Si le nombre est 123, les interprétations possibles donnent 18, 14 et 36.)
Andreas Rejbrand

23
@ilkkachu: "la somme des chiffres du nombre au carré". Eh bien, "le nombre au carré" est clairement 123 ^ 2 = 15129, donc "la somme des chiffres du nombre au carré" est "la somme des chiffres de 15129", qui est évidemment 1 + 5 + 1 + 2 + 9 = 18.
Andreas Rejbrand

15
n = n * (-1)? Wut ??? Ce que votre prof recherche est le suivant: `n = -n '. Le langage C a un opérateur moins unaire.
Kaz

Réponses:


245

Résumant une discussion qui a percolé dans les commentaires:

  • Il n'y a aucune bonne raison de tester à l'avance n == 0. Le while(n)test traitera parfaitement ce cas.
  • Il est probable que votre professeur soit encore habitué aux temps antérieurs, lorsque le résultat des %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 de a % 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 pour n < 0serait 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 % 10garantit le dernier chiffre (éventuellement négatif) de nmême quand nest négatif.

Donc, la réponse à la question "Quel est le sens de while(n)?" est "Exactement le même que while(n != 0)" et la réponse à "Ce code fonctionnera-t-il correctement aussi bien pour le négatif que pour le positif n?" 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.


39
"Il n'y a aucune bonne raison de tester à l'avance pour n == 0" - techniquement, c'est correct. Mais étant donné que nous parlons d'un professeur dans un cadre d'enseignement, ils peuvent apprécier la clarté sur la brièveté beaucoup plus que nous. L'ajout du test supplémentaire pour n == 0au 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 de sreturn est la bonne.
ilkkachu

22
De plus, le professeur peut vouloir savoir que l'étudiant est conscient et a réfléchi à pourquoi et comment la fonction se comporte avec une entrée de zéro (c'est-à-dire qu'elle ne renvoie pas la valeur correcte par accident ). Ils ont peut-être rencontré des étudiants qui ne réalisent pas ce qui se passerait dans ce cas, que la boucle peut être exécutée zéro fois, etc. ..
ilkkachu

36
@ilkkachu Si tel est le cas, l'enseignant doit distribuer une tâche qui nécessite un tel test pour fonctionner correctement.
klutt

38
@ilkkachu Eh bien, je prends votre point dans le cas général, parce que j'apprécie absolument la clarté sur la brièveté - et à peu près tout le temps, pas nécessairement seulement dans un cadre pédagogique. Mais cela dit, parfois la brièveté est la clarté, et si vous pouvez arranger le code principal pour gérer à la fois le cas général et les cas de bord, sans encombrer le code (et l'analyse de couverture) avec des cas spéciaux pour les cas de bord , c'est une belle chose! Je pense que c'est quelque chose à apprécier même au niveau débutant.
Steve Summit

49
@ilkkachu Par cet argument, vous devriez sûrement aussi ajouter des tests pour n = 1 et ainsi de suite. Il n'y a rien de spécial 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.
Voo

107

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 nest 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 cela while(n)devrait être changé en while(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 :

Si le quotient a / b est représentable, l'expression (a / b) * b + a% b doit être égale à a; sinon, le comportement de a / b et de% b n'est pas défini.

La note de bas de page dit ceci:

Ceci est souvent appelé "troncature vers zéro".

La troncature vers zéro signifie que la valeur absolue de a/best égale à la valeur absolue de (-a)/bpour tous aet b, 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%bselon 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==1mais (-7)%(-3)==(-1).

Voici un extrait de démonstration:

$ 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

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_MINET 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 ligne n = 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 npeut ê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'utilisation intpeut sembler sécuriser son code, sauf qu'il intne 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 avec INT_MINmentionné ci-dessus avec sa version. Pour éviter cela, vous pouvez écrire à la longplace de int, qui est un entier 32 bits sur l'une ou l'autre architecture. A longest garanti pour pouvoir contenir n'importe quelle valeur dans la plage [-2147483647; 2147483647]. La norme C11 5.2.4.2.1 LONG_MIN est souvent-2147483648mais la valeur maximale (oui, maximale, c'est un nombre négatif) autorisée pour LONG_MINest 2147483647.

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.

  • Les noms des variables pourraient être un peu meilleurs, mais c'est une fonction courte qui est facile à comprendre, donc ce n'est pas grave.
  • Vous pouvez modifier la condition de nà n!=0. Sémantiquement, c'est 100% équivalent, mais cela le rend un peu plus clair.
  • Déplacer la déclaration de c(que j'ai renommé digit) à l'intérieur de la boucle while car elle n'est utilisée que là-bas.
  • Modifiez le type d'argument pour longvous assurer qu'il peut gérer l'ensemble des entrées.
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

En fait, cela peut être un peu trompeur car - comme mentionné ci-dessus - la variable digitpeut 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.

  • Passez sum += (digit * digit)à sum += ((n%10)*(n%10))la variable et ignorez-la digitcomplètement.
  • Changez le signe de digitsi 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.
  • Créez une fonction distincte qui extrait le dernier chiffre. 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.
  • Nommez-le simplement ccomme 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. :)


19
Si l'entrée est INT_MINet 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. ;-)
Andrew Henle

5
Il convient de mentionner qu'en plus (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.)
Leftaroundabout

7
@MartinRosenau Vous dites "pourrait". Êtes-vous sûr que cela se produit réellement ou que cela est autorisé par la norme ou quelque chose ou êtes-vous simplement en train de spéculer?
klutt

6
@MartinRosenau: D'accord, mais l'utilisation de ces commutateurs ne ferait plus du C. GCC / clang n'ont pas de commutateurs qui cassent la division entière sur les ISA que je connais. Même si ignorer le bit de signe pourrait peut-être donner une accélération en utilisant l'inverse multiplicatif normal pour des diviseurs constants. (Mais tous les ISA que je connais qui ont une instruction de division matérielle l'implémentent de la manière C99, tronquant vers zéro, donc C %et les /opérateurs peuvent compiler en juste un idivsur x86, ou sdivsur ARM ou autre chose. Pourtant, cela n'a rien à voir avec le code-gen plus rapide pour les diviseurs à constante de temps de compilation)
Peter Cordes

5
@TonyK AFIK, c'est ainsi que cela est généralement résolu, mais selon la norme, c'est UB.
klutt

20

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é * /


9
Les C %sont mieux appelés un reste , car c'est le cas, même pour les types signés.
Peter Cordes

15
La quadrature est importante, mais je pense que c'est la partie évidente. Ce qui doit être souligné, c'est que (par exemple) -7 % 10 sera en fait-7 plutôt que 3.
Jacob Raihle

5
«Module mathématique approprié» ne veut rien dire. Le terme correct est «modulo euclidien» (attention au suffixe!) Et c'est bien ce que l' %opérateur de C n'est pas.
Jan Hudec

J'aime cette réponse car elle règle la question des multiples façons d'interpréter le modulo. Ne laissez jamais une chose pareille au hasard / à l'interprétation. Ce n'est pas du golf de code.
Harper - Réintègre Monica

1
"le bon module mathématique est toujours non négatif" - Pas vraiment. Le résultat d'une opération modulo est une classe d'équivalence , mais il est courant de traiter le résultat comme le plus petit nombre non négatif appartenant à cette classe.
klutt

10

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:

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

En C ++, c'est essentiellement la même chose que :

if (!n) {
        return 0;
}

Cela signifie que votre temps équivaut à quelque chose comme ceci:

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

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:

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

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 :

L'opérateur binaire / donne le quotient, et l'opérateur binaire% donne le reste de la division de la première expression par la seconde. Si le deuxième opérande de / ou% est nul, le comportement n'est pas défini. Pour les opérandes intégraux, l'opérateur / fournit le quotient algébrique avec toute partie fractionnaire rejetée; si le quotient a / b est représentable dans le type du résultat, (a / b) * b + a% b est égal à a.

Et ensuite:

Si les deux opérandes sont non négatifs, le reste est non négatif; sinon, le signe du reste est défini par l'implémentation.

Comme l'a correctement montré l'affiche de la réponse citée, la partie importante de cette équation ici:

(a / b) * b + a% b

En prenant un exemple de votre cas, vous obtiendrez quelque chose comme ceci:

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

Le seul hic, c'est cette dernière ligne:

Si les deux opérandes sont non négatifs, le reste est non négatif; sinon, le signe du reste est défini par l'implémentation.

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 :

C99 exige que lorsque a / b est représentable:

(a / b) * b + a% b est égal à a

Et un autre endroit :

Lorsque les entiers sont divisés et que la division est inexacte, si les deux opérandes sont positifs, le résultat de l'opérateur / est le plus grand entier inférieur au quotient algébrique et le résultat de l'opérateur% est positif. Si l'un des opérandes est négatif, que le résultat de l'opérateur / soit le plus grand entier inférieur au quotient algébrique ou le plus petit entier supérieur au quotient algébrique est défini par l'implémentation, tout comme le signe du résultat de l'opérateur%. Si le quotient a / b est représentable, l'expression (a / b) * b + a% b doit être égale à a.

ANSI C ou ISO C spécifie-t-il ce que -5% 10 devrait être?

Donc voilà. Même en C99, cela ne semble pas vous affecter. L'équation est la même.


1
Les parties que vous avez citées ne prennent pas en charge cette réponse. «le signe du reste est défini par l'implémentation» ne signifie pas que (-1)%10pourrait produire -1ou 1; cela signifie qu'il pourrait produire -1ou 9, et dans ce dernier cas, (-1)/10il produira -1et le code OP ne se terminera jamais.
stewbasic

Pourriez-vous indiquer une source pour cela? J'ai beaucoup de mal à croire que (-1) / 10 est -1. Cela devrait être 0. De plus, (-1)% 10 = 9 semble violer l'équation gouvernante.
Chipster

1
@Chipster, commencez par (a/b)*b + a%b == a, puis laissez a=-1; b=10, donner (-1/10)*10 + (-1)%10 == -1. Maintenant, si en -1/10effet est arrondi vers le bas (vers -inf), alors nous l'avons (-1/10)*10 == -10, et vous devez avoir (-1)%10 == 9pour 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.
ilkkachu

1
@Chipster La source est les extraits que vous avez cités. Notez que (-1)*10+9=-1, donc le choix (-1)/10=-1et (-1)%10=9ne viole pas l'équation gouvernante. D'un autre côté, le choix (-1)%10=1ne peut pas satisfaire l'équation dominante, quelle que soit la façon dont il (-1)/10est choisi; il n'y a aucun entier qtel que q*10+1=-1.
stewbasic

8

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:

if (n < 0) 
    n = -n;

ou au moins un commentaire:

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

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?


1
Que N soit négatif, N au carré sera positif. Alors, pourquoi retirer le signe en premier lieu? -3 * -3 = 9; 3 * 3 = 9. Ou les mathématiques ont-elles changé au cours des 30 années impaires depuis que j'ai appris cela?
Merovex

2
@CB Pour être honnête, je n'ai même pas remarqué que n pouvait être négatif pendant que j'écrivais le test, mais quand il est revenu, j'avais juste le sentiment que la boucle while ne serait pas sautée, même si le nombre était négatif. J'ai fait quelques tests sur mon ordinateur et cela a confirmé mon scepticisme. Après cela, j'ai posté cette question. Donc non, je ne réfléchissais pas si profondément en écrivant le code.
user010517720

5

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 321serait 3.

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 1et 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:

écrire une fonction en C / C ++ qui retourne la somme des chiffres du nombre au carré

J'aurais certainement fourni deux réponses:

  • la bonne réponse (en mettant d'abord le nombre au carré), et
  • la réponse incorrecte conformément à l'exemple, juste pour le garder heureux ;-)

5
Et aussi un troisième au carré de la somme des chiffres?
kriss

@kriss - ouais, je ne suis pas si intelligent :-(
SlowLearner

1
J'ai également eu ma part d'affectations trop vagues dans mon temps d'étudiant. Un enseignant voulait une bibliothèque de manipulation un peu mais il a été surpris par mon code et a dit que cela ne fonctionnait pas. J'ai dû lui faire remarquer qu'il n'avait jamais défini l'endianité dans son affectation et il a choisi le bit inférieur comme bit 0 pendant que je faisais l'autre choix. La seule partie ennuyeuse est qu'il aurait dû être capable de comprendre où était la différence sans que je le lui dise.
kriss

1

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.


2
J'ai programmé en C seulement une poignée de fois et je sais que les ++iincréments avant l'évaluation et les i++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 comme if (foo == TRUE). Je suis d'accord sur les noms de variables, cependant.
Alan Ocallaghan

3
Généralement, ce n'est pas un mauvais conseil, mais éviter les fonctionnalités de base du langage (que les gens rencontreront inévitablement de toute façon) à des fins de programmation défensive est excessif. Un code court et clair est souvent plus lisible de toute façon. Nous ne parlons pas ici de perl-fou ou de bash-one-liners, juste des fonctionnalités de langage vraiment basiques.
Alan Ocallaghan

1
Je ne suis pas sûr de savoir pourquoi les votes négatifs sur cette réponse ont été donnés. Pour moi, tout ce qui est dit ici est vrai et important et un bon conseil pour chaque développeur. Surtout la partie de programmation intelligente, même si ce while(n)n'est pas le pire exemple pour ça (j'aime le if(strcmp(one, two))plus)
Kai Huppmann

1
Et en plus, vous ne devez vraiment laisser personne qui ne connaît pas la différence entre i++ et ++imodifier le code C qui devrait être utilisé en production.
klutt

2
@PaulMcCarthy Nous sommes tous les deux pour la lisibilité du code. Mais nous ne sommes pas d'accord sur ce que cela signifie. De plus, ce n'est pas objectif. Ce qui est facile à lire pour une personne peut être difficile pour une autre. La personnalité et les connaissances de base affectent fortement cela. Mon point principal est que vous n'obtenez pas une lisibilité maximale en suivant aveuglément certaines règles.
klutt

0

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.


4
"Extra return est une instruction goto et nous n'utilisons pas goto en C." - c'est une combinaison d'une généralisation très large et d'un tronçon très éloigné.
SS Anne

1
"Nous" utilisons définitivement goto en C. Il n'y a rien de mal à cela.
klutt

1
De plus, il n'y a rien de mal avec une fonction commeint 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 Je ne préconise pas un code difficile à lire, mais j'ai vu des tonnes d'exemples où le code ressemble à un gâchis complet juste pour éviter un simple goto ou une déclaration de retour supplémentaire. Voici du code avec une utilisation appropriée de goto. Je vous mets au défi de supprimer les instructions goto tout en facilitant la lecture et le maintien du code: pastebin.com/aNaGb66Q
klutt

1
@PeterKrassoi Veuillez également montrer votre version de ceci: pastebin.com/qBmR78KA
klutt

0

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:

Écrivez une fonction dans le sous-ensemble commun de C et C ++ qui prend un entier ndans la plage [-10 7 , 10 7 ] et renvoie la somme des carrés des chiffres de sa représentation en base 10. Exemple: si nc'est le cas 123, votre fonction devrait revenir 14(1 2 + 2 2 + 3 2 = 14).

La fonction que vous avez écrite est correcte sauf pour 2 détails:

  • L'argument doit avoir un type longpour tenir compte de toutes les valeurs dans la plage spécifiée car le type longest 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 type intest suffisant pour le type de retour, dont la valeur maximale est 568.)
  • Le comportement des %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:

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 réponse de l'enseignant a plusieurs défauts:

  • le type intpeut avoir une plage de valeurs insuffisante.
  • il n'est pas nécessaire de cas particulier la valeur 0.
  • la négation des valeurs négatives est inutile et peut avoir un comportement indéfini pour 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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.