¿Necesito manejar explícitamente números negativos o cero al sumar dígitos cuadrados?


220

Recientemente tuve una prueba en mi clase. Uno de los problemas fue el siguiente:

Dado un número n , escriba una función en C / C ++ que devuelva la suma de los dígitos del número al cuadrado . (Lo siguiente es importante). El rango de n es [- (10 ^ 7), 10 ^ 7]. Ejemplo: si n = 123, su función debería devolver 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Esta es la función que escribí:

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

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

    return s;
}

Me pareció bien. Así que ahora regresó la prueba y descubrí que el maestro no me dio todos los puntos por una razón que no entiendo. Según él, para que mi función esté completa, debería haber agregado el siguiente detalle:

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;
}

El argumento para esto es que el número n está en el rango [- (10 ^ 7), 10 ^ 7], por lo que puede ser un número negativo. Pero no veo dónde falla mi propia versión de la función. Si entiendo correctamente, el significado de while(n)es while(n != 0), no while (n > 0) , así que en mi versión de la función, el número n no dejaría de entrar en el bucle. Funcionaría igual.

Luego, probé ambas versiones de la función en mi computadora en casa y obtuve exactamente las mismas respuestas para todos los ejemplos que probé. Entonces, sum_of_digits_squared(-123)es igual a sum_of_digits_squared(123)(que de nuevo, es igual a 14) (incluso sin los detalles que aparentemente debería haber agregado). De hecho, si trato de imprimir en la pantalla los dígitos del número (de menor a mayor importancia), en el 123caso que obtengo 3 2 1y en el -123caso que obtengo -3 -2 -1(lo que en realidad es algo interesante). Pero en este problema no importaría ya que cuadramos los dígitos.

Entonces, ¿quién está equivocado?

EDITAR : Mi mal, olvidé especificar y no sabía que era importante. La versión de C utilizada en nuestra clase y pruebas tiene que ser C99 o más reciente . Así que supongo (al leer los comentarios) que mi versión obtendría la respuesta correcta de cualquier manera.


120
n = n * (-1)es una forma ridícula de escribir n = -n; Solo un académico lo pensaría. Y mucho menos agregar los paréntesis redundantes.
user207421

32
Escriba una serie de pruebas unitarias para verificar si una implementación dada se ajusta a la especificación. Si hay un problema (funcional) con un fragmento de código, debería ser posible escribir una prueba que demuestre el resultado incorrecto dada una entrada particular.
Carl

94
Me parece interesante que "la suma de los dígitos del número al cuadrado" se puede interpretar de tres (3) formas completamente diferentes. (Si el número es 123, las posibles interpretaciones dan 18, 14 y 36.)
Andreas Rejbrand el

23
@ilkkachu: "la suma de los dígitos del número al cuadrado". Bueno, "el número al cuadrado" es claramente 123 ^ 2 = 15129, entonces "la suma de los dígitos del número al cuadrado" es "la suma de los dígitos de 15129", que obviamente es 1 + 5 + 1 + 2 + 9 = 18.
Andreas Rejbrand

15
n = n * (-1)? Wut ??? Lo que busca tu profesor es esto: `n = -n '. El lenguaje C tiene un operador unario menos.
Kaz

Respuestas:


245

Resumiendo una discusión que se ha filtrado en los comentarios:

  • No hay una buena razón para realizar la prueba por adelantado n == 0. La while(n)prueba manejará ese caso perfectamente.
  • Es probable que su maestro aún esté acostumbrado a épocas anteriores, cuando el resultado de %los operandos negativos se definió de manera diferente. En algunos sistemas antiguos (incluido, en particular, Unix temprano en un PDP-11, donde Dennis Ritchie desarrolló originalmente C), el resultado siemprea % b estuvo en el rango , lo que significa que -123% 10 fue 7. En dicho sistema, la prueba de antemano para sería necesario.[0 .. b-1]n < 0

Pero la segunda viñeta se aplica solo a tiempos anteriores. En las versiones actuales de los estándares C y C ++, la división de enteros se define para truncarse hacia 0, por lo que n % 10se garantiza que le dará el último dígito (posiblemente negativo) nincluso cuando nes negativo.

Entonces, la respuesta a la pregunta "¿Cuál es el significado de while(n)?" es "Exactamente igual que while(n != 0)" y la respuesta a "¿Funcionará este código correctamente tanto para negativo como para positivo n?" es "Sí, en cualquier compilador moderno que cumpla con los estándares". La respuesta a la pregunta "¿Entonces por qué el instructor lo marcó?" es probable que no estén al tanto de una redefinición significativa del lenguaje que le sucedió a C en 1999 y a C ++ en 2010 más o menos.


39
"No hay una buena razón para probar por adelantado n == 0" - técnicamente, eso es correcto. Pero dado que estamos hablando de un profesor en un entorno de enseñanza, pueden apreciar la claridad sobre la brevedad mucho más que nosotros. Agregar la prueba adicional por lo n == 0menos hace que sea inmediatamente y completamente obvio para cualquier lector lo que sucede en ese caso. Sin ella, el lector debe asegurarse de que el bucle se omite y que el valor predeterminado de sreturn es el correcto.
ilkkachu

22
Además, el profesor puede querer saber que el alumno es consciente y ha pensado por qué y cómo se comporta la función con una entrada de cero (es decir, que no devuelve el valor correcto por accidente ). Es posible que hayan conocido estudiantes que no se dan cuenta de lo que sucedería en ese caso, que el ciclo puede ejecutarse cero veces, etc. ..
ilkkachu

36
@ilkkachu Si ese es el caso, entonces el maestro debe entregar una tarea que requiera que dicha prueba funcione correctamente.
klutt

38
@ilkkachu Bueno, entiendo su punto en el caso general, porque aprecio absolutamente la claridad sobre la brevedad, y casi todo el tiempo, no necesariamente solo en un entorno pedagógico. Pero dicho esto, a veces la brevedad es claridad, y si puede hacer arreglos para que el código principal maneje tanto el caso general como el (los) caso (s) de borde, sin saturar el código (y el análisis de cobertura) con casos especiales para los casos de borde , eso es una cosa hermosa! Creo que es algo para apreciar incluso a nivel principiante.
Steve Summit

49
@ilkkachu Según ese argumento, seguramente también debería agregar pruebas para n = 1 y así sucesivamente. No tiene nada de especial n=0. Introducir ramificaciones y complicaciones innecesarias no hace que el código sea más fácil, lo hace más difícil, ya que ahora no solo tiene que demostrar que el algoritmo general es correcto, sino que también debe pensar en todos los casos especiales por separado.
Voo

107

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 nes 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/bes igual al valor absoluto (-a)/bpara todos ay 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%bacuerdo 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==1pero (-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_MINY, 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 npuede 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 intpodría hacer que su código sea seguro, excepto que intno 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_MINmencionado anteriormente con su versión. Para evitar esto, puede escribir en longlugar de int, que es un número entero de 32 bits en cualquier arquitectura. Se longgarantiza que A puede mantener cualquier valor en el rango [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1 LONG_MIN es a menudo-2147483648pero el valor máximo permitido (sí, máximo, es un número negativo) para LONG_MINes 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 na 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 longpara 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 digitpuede 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 digitpor completo.
  • Cambia el signo de digitsi 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 ccomo 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. :)


19
Si la entrada es INT_MINy la arquitectura está usando el complemento de dos (que es muy común), entonces el código de su maestro producirá un comportamiento indefinido. Ay. Eso dejará una marca. ;-)
Andrew Henle

55
Cabe mencionar que, además de (a/b)*b + a%b ≡ a, el código del OP también depende del hecho de que se /redondea hacia cero, y eso (-c)*(-c) ≡ c*c. Se podría argumentar que las verificaciones adicionales están justificadas a pesar de que el estándar garantiza todo eso, porque no es lo suficientemente obvio. (Por supuesto que también se podría argumentar que no debería ser más bien un comentario que une las secciones estándar pertinentes, pero las directrices de estilo puede variar.)
leftaroundabout

77
@MartinRosenau Dices "poder". ¿Estás seguro de que esto realmente está sucediendo o que está permitido por el estándar o algo así o solo especulas?
klutt

66
@MartinRosenau: Ok, pero usar esos interruptores ya no haría que C GCC / clang no tiene ningún interruptor que rompa la división de enteros en ninguna ISA que yo sepa. Aunque ignorar el bit de signo podría dar una aceleración usando el inverso multiplicativo normal para divisores constantes. (Pero todas las ISA que conozco que tienen una instrucción de división de hardware la implementan de la manera C99, truncando hacia cero, para que C %y los /operadores puedan compilar solo en un idivx86, o sdiven ARM o lo que sea. Sin embargo, eso no está relacionado code-gen más rápido para divisores constantes de tiempo de compilación)
Peter Cordes

55
@TonyK AFIK, así es como generalmente se resuelve, pero según el estándar es UB.
klutt

20

No me gusta por completo ni tu versión ni la de tu profesor. La versión de tu profesor hace innecesarios los exámenes adicionales que señalas correctamente. El operador mod de C no es un mod matemático apropiado: un número negativo mod 10 producirá un resultado negativo (el módulo matemático adecuado siempre es no negativo). Pero como lo estás cuadrando de todos modos, no hay diferencia.

Pero esto está lejos de ser obvio, por lo que agregaría a su código no los controles de su maestro, sino un gran comentario que explica por qué funciona. P.ej:

/ * NOTA: Esto funciona para valores negativos, porque el módulo se eleva al cuadrado * /


99
C de %se llama el mejor de un resto , porque es que, incluso para este tipo firmados.
Peter Cordes

15
La cuadratura es importante, pero creo que esa es la parte obvia. Lo que debe señalarse es que (por ejemplo) en -7 % 10 realidad será en-7 lugar de 3.
Jacob Raihle

55
"Módulo matemático adecuado" no significa nada. El término correcto es "módulo euclidiano" (¡cuidado con el sufijo!) Y eso es lo que el %operador de C no es.
Jan Hudec

Me gusta esta respuesta porque resuelve la cuestión de múltiples formas de interpretar el módulo. Nunca deje una cosa así al azar / interpretación. Esto no es código golf.
Harper - Restablece a Monica el

1
"El módulo matemático adecuado es siempre no negativo" - En realidad no. El resultado de una operación de módulo es una clase de equivalencia , pero es común tratar el resultado como el número no negativo más pequeño que pertenece a esa clase.
klutt

10

NOTA: Mientras escribía esta respuesta, usted aclaró que está usando C. La mayoría de mi respuesta es sobre C ++. Sin embargo, dado que su título todavía tiene C ++ y la pregunta todavía está etiquetada como C ++, he elegido responder de todos modos en caso de que esto sea útil para otras personas, especialmente porque la mayoría de las respuestas que he visto hasta ahora son en su mayoría insatisfactorias.

En C ++ moderno (Nota: Realmente no sé dónde se encuentra C en esto), su profesor parece estar equivocado en ambos aspectos.

Primero es esta parte aquí:

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

En C ++, esto es básicamente lo mismo que :

if (!n) {
        return 0;
}

Eso significa que tu while es equivalente a algo como esto:

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

Eso significa que dado que simplemente está saliendo de su if cuando el while no se ejecuta de todos modos, realmente no hay una razón para poner esto si aquí, ya que lo que está haciendo después del ciclo y en el if son equivalentes de todos modos. Aunque debería decir que es por alguna razón, estos eran diferentes, necesitarías tener esto si.

Entonces, realmente, esta declaración if no es particularmente útil a menos que me equivoque.

La segunda parte es donde las cosas se ponen difíciles:

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

El corazón del problema es cuál es la salida del módulo de un número negativo.

En C ++ moderno, esto parece estar bien definido :

El operador / binario produce el cociente, y el operador% binario produce el resto de la división de la primera expresión por la segunda. Si el segundo operando de / o% es cero, el comportamiento es indefinido. Para operandos integrales, el operador / produce el cociente algebraico con cualquier parte fraccional descartada; si el cociente a / b es representable en el tipo del resultado, (a / b) * b + a% b es igual a a.

Y después:

Si ambos operandos no son negativos, el resto no es negativo; si no, el signo del resto está definido por la implementación.

Como el cartel de la respuesta citada señala correctamente, la parte importante de esta ecuación aquí:

(a / b) * b + a% b

Tomando un ejemplo de su caso, obtendría algo como esto:

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

El único inconveniente es esa última línea:

Si ambos operandos no son negativos, el resto no es negativo; si no, el signo del resto está definido por la implementación.

Eso significa que en un caso como este, solo el signo parece estar definido por la implementación. Eso no debería ser un problema en su caso porque, porque de todos modos está cuadrando este valor.

Dicho esto, tenga en cuenta que esto no necesariamente se aplica a versiones anteriores de C ++ o C99. Si eso es lo que está usando su profesor, ese podría ser el motivo.


EDITAR: No, estoy equivocado. Este parece ser el caso para C99 o posterior también :

C99 requiere que cuando a / b sea representable:

(a / b) * b + a% b será igual a

Y otro lugar :

Cuando los enteros se dividen y la división es inexacta, si ambos operandos son positivos, el resultado del operador / es el entero más grande menor que el cociente algebraico y el resultado del operador% es positivo. Si cualquiera de los operandos es negativo, si el resultado del operador / es el entero más grande menor que el cociente algebraico o el entero más pequeño mayor que el cociente algebraico está definido por la implementación, como lo es el signo del resultado del operador%. Si el cociente a / b es representable, la expresión (a / b) * b + a% b será igual a a.

¿ANSI C o ISO C especifican qué -5% 10 debería ser?

Así que sí. Incluso en C99, esto no parece afectarlo. La ecuación es la misma.


1
Las partes que ha citado no respaldan esta respuesta. "el signo del resto es la implementación definida" no significa que (-1)%10podría producir -1o 1; significa que podría producir -1o 9, y en este último caso (-1)/10producirá -1y el código de OP nunca terminará.
stewbasic

¿Podría señalar una fuente para esto? Me cuesta mucho creer que (-1) / 10 es -1. Eso debería ser 0. Además, (-1)% 10 = 9 parece violar la ecuación de gobierno.
Chipster

1
@Chipster, comienza con (a/b)*b + a%b == a, luego deja a=-1; b=10, dando (-1/10)*10 + (-1)%10 == -1. Ahora, si de -1/10hecho se redondea hacia abajo (hacia -inf), entonces lo tenemos (-1/10)*10 == -10, y usted necesita tener (-1)%10 == 9para que la primera ecuación coincida. Como el otro estado de respuestas , no es así como funciona dentro de los estándares actuales, pero es como solía funcionar. No es realmente acerca de la señal del resto como tal, sino cómo las rondas de división y de lo que el resto continuación, tiene que ser para satisfacer la ecuación.
ilkkachu

1
@Chipster La fuente son los fragmentos que ha citado. Tenga en cuenta que (-1)*10+9=-1, por lo que la elección (-1)/10=-1y (-1)%10=9no viola la ecuación de gobierno. Por otro lado, la elección (-1)%10=1no puede satisfacer la ecuación de gobierno sin importar cómo (-1)/10se elija; no hay un entero qtal que q*10+1=-1.
stewbasic el

8

Como otros han señalado, el tratamiento especial para n == 0 no tiene sentido, ya que para cada programador de C serio es obvio que "while (n)" hace el trabajo.

El comportamiento para n <0 no es tan obvio, por eso preferiría ver esas 2 líneas de código:

if (n < 0) 
    n = -n;

o al menos un comentario:

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

Honestamente, ¿a qué hora comenzaste a considerar que n podría ser negativo? ¿Al escribir el código o al leer los comentarios de tu maestro?


1
Independientemente de si N es negativo, N al cuadrado será positivo. Entonces, ¿por qué eliminar el signo en primer lugar? -3 * -3 = 9; 3 * 3 = 9. ¿O las matemáticas han cambiado en los últimos 30 años desde que aprendí esto?
Merovex

2
@CB Para ser justos, ni siquiera me di cuenta de que n podría ser negativo mientras escribía la prueba, pero cuando regresó, tuve la sensación de que el ciclo while no se omitiría, incluso si el número es negativo. Hice algunas pruebas en mi computadora y confirmó mi escepticismo. Después de eso, publiqué esta pregunta. Entonces no, no estaba pensando tan profundamente mientras escribía el código.
user010517720

5

Esto me recuerda una tarea que fallé

Allá por los años 90. El profesor había estado surgiendo sobre bucles y, para resumir, nuestra tarea consistía en escribir una función que devolviera el número de dígitos para cualquier entero> 0.

Entonces, por ejemplo, el número de dígitos 321sería 3.

Aunque la tarea simplemente decía que escribiera una función que devolviera el número de dígitos, la expectativa era que usaríamos un bucle que se dividiera por 10 hasta que ... lo obtenga, como lo cubre la conferencia .

Pero el uso de bucles no se indicó explícitamente, así que yo: took the log, stripped away the decimals, added 1y posteriormente fui criticado frente a toda la clase.

El punto es que el propósito de la tarea era evaluar nuestra comprensión de lo que habíamos aprendido durante las conferencias . De la conferencia que recibí, supe que el profesor de informática era un poco idiota (¿pero quizás un idiota con un plan?)


En su situación:

escribe una función en C / C ++ que devuelva la suma de los dígitos del número al cuadrado

Definitivamente habría proporcionado dos respuestas:

  • la respuesta correcta (cuadrando el número primero), y
  • la respuesta incorrecta de acuerdo con el ejemplo, solo para mantenerlo feliz ;-)

55
¿Y también un tercero que cuadra la suma de los dígitos?
kriss

@kriss: sí, no soy tan inteligente :-(
SlowLearner

1
También tuve mi parte de asignaciones demasiado vagas en mi tiempo de estudiante. Un maestro quería una pequeña biblioteca de manipulación, pero mi código lo sorprendió y dijo que no estaba funcionando. Tuve que señalarle que nunca definió endianness en su asignación y eligió el bit más bajo como bit 0 mientras yo hacía la otra opción. La única parte molesta es que debería haber podido descubrir dónde estaba la diferencia sin que yo le dijera.
kriss

1

En general, en las asignaciones no todas las marcas se dan solo porque el código funciona. También obtiene calificaciones por hacer que una solución sea fácil de leer, eficiente y elegante. Estas cosas no siempre son mutuamente excluyentes.

Uno que no puedo entender lo suficiente es "usar nombres de variables significativos" .

En su ejemplo, no hace mucha diferencia, pero si está trabajando en un proyecto con un millón de líneas de legibilidad de código se vuelve muy importante.

Otra cosa que tiendo a ver con el código C es que las personas intentan parecer inteligentes. En lugar de usar while (n! = 0), les mostraré a todos lo inteligente que soy al escribir while (n) porque significa lo mismo. Bueno, lo hace en el compilador que tiene, pero como sugirió, la versión anterior del profesor no lo ha implementado de la misma manera.

Un ejemplo común es hacer referencia a un índice en una matriz mientras lo incrementa al mismo tiempo; Números [i ++] = iPrime;

Ahora, el próximo programador que trabaja en el código tiene que saber si me incrementan antes o después de la tarea, solo para que alguien pueda presumir.

Un megabyte de espacio en disco es más barato que un rollo de papel higiénico. Si busca claridad en lugar de tratar de ahorrar espacio, sus compañeros programadores estarán más felices.


2
He programado en C solo un puñado de veces y sé que ++iaumenta antes de la evaluación e i++incrementos después. while(n)También es una característica común del lenguaje. Basado en una lógica como esta, he visto muchos códigos similares if (foo == TRUE). Estoy de acuerdo en re: nombres de variables, sin embargo.
alan ocallaghan

3
En general, eso no es un mal consejo, pero evitar las características básicas del lenguaje (que la gente inevitablemente encontrará de todos modos) con el propósito de la programación defensiva es excesivo. El código corto y claro a menudo es más legible de todos modos. No estamos hablando de locura perl o bash one-liners aquí, solo características de lenguaje realmente básicas.
alan ocallaghan

1
No estoy seguro, para qué se dieron los votos negativos en esta respuesta. Para mí, todo lo dicho aquí es verdadero e importante y un buen consejo para cada desarrollador. Especialmente la parte de programación inteligente, aunque while(n)no es el peor ejemplo para eso ("me gusta" if(strcmp(one, two))más)
Kai Huppmann

1
Y además, realmente no debe permitir que nadie que no sepa la diferencia entre i++y ++imodifique el código C que se debe usar en la producción.
klutt

2
@PaulMcCarthy Ambos estamos a favor de la legibilidad del código. Pero no estamos de acuerdo en lo que eso significa. Además, no es objetivo. Lo que es fácil de leer para una persona puede ser difícil para otra. La personalidad y el conocimiento de fondo afecta esto fuertemente. Mi punto principal es que no obtienes la máxima legibilidad siguiendo ciegamente algunas reglas.
klutt

0

No discutiría si la definición original o moderna de '%' es mejor, pero cualquiera que escriba dos declaraciones de retorno en una función tan corta no debería enseñar la programación en C. El retorno adicional es una declaración de goto y no usamos goto en C. Además, el código sin la verificación de cero tendría el mismo resultado, el retorno adicional lo hizo más difícil de leer.


44
"El retorno adicional es una declaración de goto y no usamos goto en C." - esa es una combinación de una generalización muy amplia y un tramo muy lejano.
SS Anne

1
"Nosotros" definitivamente usamos goto en C. No hay nada de malo en eso.
klutt

1
Además, no hay nada malo con una función comoint 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 No estoy abogando por el código difícil de leer, pero he visto toneladas de ejemplos en los que el código parece un desastre completo solo para evitar un simple goto o una declaración de devolución adicional. Aquí hay un código con el uso apropiado de goto. Te reto a que elimines las declaraciones de goto y al mismo tiempo
hagas

1
@PeterKrassoi Por favor, muestre también su versión de esto: pastebin.com/qBmR78KA
klutt

0

El enunciado del problema es confuso, pero el ejemplo numérico aclara el significado de la suma de los dígitos del número al cuadrado . Aquí hay una versión mejorada:

Escriba una función en el subconjunto común de C y C ++ que tome un número entero nen el rango [-10 7 , 10 7 ] y devuelva la suma de los cuadrados de los dígitos de su representación en la base 10. Ejemplo: si nes 123, su función debería volver 14(1 2 + 2 2 + 3 2 = 14).

La función que escribió está bien, excepto por 2 detalles:

  • El argumento debe tener un tipo longpara acomodar todos los valores en el rango especificado ya que longel estándar C garantiza que el tipo tiene al menos 31 bits de valor, por lo tanto, un rango suficiente para representar todos los valores en [-10 7 , 10 7 ] . (Tenga en cuenta que el tipo intes suficiente para el tipo de retorno, cuyo valor máximo es 568).
  • El comportamiento de %los operandos negativos no es intuitivo y su especificación varía entre el C99 Standard y las ediciones anteriores. Debe documentar por qué su enfoque es válido incluso para entradas negativas.

Aquí hay una versión modificada:

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 respuesta del profesor tiene múltiples defectos:

  • tipo intpuede tener un rango de valores insuficiente.
  • no hay necesidad de poner un caso especial al valor 0.
  • Negar los valores negativos es innecesario y puede tener un comportamiento indefinido n = INT_MIN.

Dadas las restricciones adicionales en la declaración del problema (C99 y rango de valores para n), solo el primer defecto es un problema. El código adicional aún produce las respuestas correctas.

Debería obtener una buena nota en esta prueba, pero la explicación se requiere en una prueba escrita para mostrar su comprensión de los problemas negativos n, de lo contrario, el maestro puede suponer que no estaba al tanto y que tuvo suerte. En un examen oral, habría recibido una pregunta y su respuesta la habría clavado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.