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 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/b
est égale à la valeur absolue de (-a)/b
pour tous a
et 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%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:
$ 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_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 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 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'utilisation int
peut sembler sécuriser son code, sauf qu'il int
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 avec INT_MIN
mentionné ci-dessus avec sa version. Pour éviter cela, vous pouvez écrire à la long
place de int
, qui est un entier 32 bits sur l'une ou l'autre architecture. A long
est 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-2147483648
mais la valeur maximale (oui, maximale, c'est un nombre négatif) autorisée pour LONG_MIN
est 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
long
vous 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 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.
- Passez
sum += (digit * digit)
à sum += ((n%10)*(n%10))
la variable et ignorez-la digit
complètement.
- Changez le signe de
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.
- 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
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. :)
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.