Tu código está perfectamente bien
Tienes toda la razón y tu maestro está equivocado. No hay absolutamente ninguna razón para agregar esa complejidad adicional, ya que no afecta el resultado en absoluto. Incluso introduce un error. (Vea abajo)
Primero, la verificación por separado si n
es cero es obviamente completamente innecesaria y esto es muy fácil de realizar. Para ser honesto, en realidad cuestiono la competencia de tus maestros si tiene objeciones al respecto. Pero supongo que todos pueden tener un pedo cerebral de vez en cuando. Sin embargo, creo que while(n)
debería cambiarse while(n != 0)
porque agrega un poco más de claridad sin siquiera costar una línea adicional. Sin embargo, es algo menor.
El segundo es un poco más comprensible, pero todavía está equivocado.
Esto es lo que dice el estándar C11 6.5.5.p6 :
Si el cociente a / b es representable, la expresión (a / b) * b + a% b será igual a; de lo contrario, el comportamiento de a / by a% b no está definido.
La nota al pie dice esto:
Esto a menudo se llama "truncamiento hacia cero".
El truncamiento hacia cero significa que el valor absoluto para a/b
es igual al valor absoluto (-a)/b
para todos a
y b
, lo que a su vez significa que su código está perfectamente bien.
El módulo es matemática fácil, pero puede ser contradictorio
Sin embargo, tu maestro tiene un punto que debes tener cuidado, porque el hecho de que estés cuadrando el resultado es realmente crucial aquí. Calcular de a%b
acuerdo con la definición anterior es matemática fácil, pero puede ir en contra de su intuición. Para multiplicación y división, el resultado es positivo si los operandos tienen el mismo signo. Pero cuando se trata de módulo, el resultado tiene el mismo signo que el primer operando. El segundo operando no afecta el signo en absoluto. Por ejemplo, 7%3==1
pero (-7)%(-3)==(-1)
.
Aquí hay un fragmento que lo demuestra:
$ 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
Entonces, irónicamente, tu maestro demostró su punto al estar equivocado.
El código de tu maestro es defectuoso
Sí, de hecho lo es. Si la entrada es INT_MIN
Y, la arquitectura es el complemento de dos Y el patrón de bits donde el bit de signo es 1 y todos los bits de valor son 0 NO es un valor de trampa (el uso del complemento de dos sin valores de trampa es muy común), entonces el código de su maestro producirá un comportamiento indefinido en la linea n = n * (-1)
. Su código es, aunque sea ligeramente, mejor que el suyo. Y considerando la introducción de un pequeño error al hacer que el código sea innecesario complejo y obtener un valor absolutamente cero, diría que su código es MUCHO mejor.
En otras palabras, en compilaciones donde INT_MIN = -32768 (aunque la función resultante no puede recibir una entrada que sea <-32768 o> 32767), la entrada válida de -32768 provoca un comportamiento indefinido, porque el resultado de - (- 32768i16) no se puede expresar como un entero de 16 bits. (En realidad, -32768 probablemente no causaría un resultado incorrecto, porque - (- 32768i16) generalmente se evalúa a -32768i16, y su programa maneja los números negativos correctamente.) (SHRT_MIN podría ser -32768 o -32767, dependiendo del compilador).
Pero su maestro declaró explícitamente que n
puede estar en el rango [-10 ^ 7; 10 ^ 7]. Un número entero de 16 bits es demasiado pequeño; tendrías que usar [al menos] un número entero de 32 bits. El uso int
podría hacer que su código sea seguro, excepto que int
no es necesariamente un número entero de 32 bits. Si compila para una arquitectura de 16 bits, ambos fragmentos de código tienen fallas. Pero su código sigue siendo mucho mejor porque este escenario reintroduce el error INT_MIN
mencionado anteriormente con su versión. Para evitar esto, puede escribir en long
lugar de int
, que es un número entero de 32 bits en cualquier arquitectura. Se long
garantiza que A puede mantener cualquier valor en el rango [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1 LONG_MIN
es a menudo-2147483648
pero el valor máximo permitido (sí, máximo, es un número negativo) para LONG_MIN
es 2147483647
.
¿Qué cambios haría en su código?
Su código está bien tal como está, por lo que estas no son realmente quejas. Es más así si realmente necesito decir algo sobre su código, hay algunas pequeñas cosas que podrían aclararlo un poco.
- Los nombres de las variables podrían ser un poco mejores, pero es una función corta que es fácil de entender, por lo que no es gran cosa.
- Puede cambiar la condición de
n
a n!=0
. Semánticamente, es 100% equivalente, pero lo hace un poco más claro.
- Mueve la declaración de
c
(a la que cambié el nombre digit
) dentro del ciclo while ya que solo se usa allí.
- Cambie el tipo de argumento a
long
para asegurarse de que pueda manejar todo el conjunto de entrada.
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 realidad, esto puede ser un poco engañoso porque, como se mencionó anteriormente, la variable digit
puede obtener un valor negativo, pero un dígito en sí mismo nunca es positivo o negativo. Hay algunas maneras de evitar esto, pero esto es REALMENTE quisquilloso, y no me importarían esos pequeños detalles. Especialmente la función separada para el último dígito lo lleva demasiado lejos. Irónicamente, esta es una de las cosas que el código de tu maestro realmente resuelve.
- Cambiar
sum += (digit * digit)
a sum += ((n%10)*(n%10))
y omitir la variable digit
por completo.
- Cambia el signo de
digit
si es negativo. Pero recomendaría encarecidamente no hacer que el código sea más complejo solo para que un nombre de variable tenga sentido. Ese es un olor a código MUY fuerte.
- Cree una función separada que extraiga el último dígito.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Esto es útil si desea utilizar esa función en otro lugar.
- Solo nómbrelo
c
como lo hace originalmente. Ese nombre de variable no proporciona información útil, pero por otro lado, tampoco es engañoso.
Pero para ser honesto, en este punto debes pasar a un trabajo más importante. :)
n = n * (-1)
es una forma ridícula de escribirn = -n
; Solo un académico lo pensaría. Y mucho menos agregar los paréntesis redundantes.