Seu código está perfeitamente bem
Você está absolutamente correto e seu professor está errado. Não há absolutamente nenhuma razão para adicionar essa complexidade extra, pois não afeta o resultado. Ele ainda apresenta um bug. (Ver abaixo)
Primeiro, a verificação separada de n
zero é obviamente completamente desnecessária e isso é muito fácil de realizar. Para ser sincero, na verdade questiono a competência de seus professores se ele tiver objeções a esse respeito. Mas acho que todo mundo pode ter um peido cerebral de vez em quando. No entanto, acho que isso while(n)
deve ser alterado parawhile(n != 0)
porque adiciona um pouco de clareza extra, sem mesmo custar uma linha extra. É uma coisa menor, no entanto.
O segundo é um pouco mais compreensível, mas ele ainda está errado.
Isto é o que o padrão C11 6.5.5.p6 diz:
Se o quociente a / b for representável, a expressão (a / b) * b + a% b será igual a; caso contrário, o comportamento de a / be% b é indefinido.
A nota de rodapé diz o seguinte:
Isso geralmente é chamado de "truncamento em direção a zero".
Truncamento em direção a zero significa que o valor absoluto para a/b
é igual ao valor absoluto (-a)/b
para todos a
eb
, o que, por sua vez, significa que seu código está perfeitamente correto.
O módulo é fácil de matemática, mas pode ser contra-intuitivo
No entanto, seu professor tem um ponto em que você deve ter cuidado, porque o fato de você estar obtendo o resultado é realmente crucial aqui. Calcular de a%b
acordo com a definição acima é uma matemática fácil, mas pode ir contra a sua intuição. Para multiplicação e divisão, o resultado é positivo se os operandos tiverem sinal de igual. Mas quando se trata de módulo, o resultado tem o mesmo sinal que o primeiro operando. O segundo operando não afeta o sinal. Por exemplo, 7%3==1
mas(-7)%(-3)==(-1)
.
Aqui está um trecho de demonstração:
$ 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
Então, ironicamente, seu professor provou o que estava errado.
O código do seu professor está com defeito
Sim, é mesmo. Se a entrada é INT_MIN
AND a arquitetura é o complemento de dois E o padrão de bits em que o bit de sinal é 1 e todos os bits de valor são 0 NÃO é um valor de interceptação (o uso do complemento de dois sem valores de interceptação é muito comum), o código do seu professor produzirá um comportamento indefinido na linha n = n * (-1)
. Seu código é - ainda que um pouco - melhor que o dele. E, considerando a introdução de um pequeno bug, tornando o código desnecessário complexo e ganhando absolutamente zero valor, eu diria que seu código é MUITO melhor.
Em outras palavras, em compilações em que INT_MIN = -32768 (mesmo que a função resultante não possa receber uma entrada <-32768 ou> 32767), a entrada válida de -32768 causa comportamento indefinido, porque o resultado de - (- 32768i16) não pode ser expresso como um número inteiro de 16 bits. (Na verdade, -32768 provavelmente não causaria um resultado incorreto, porque - (- 32768i16) geralmente é avaliado para -32768i16 e seu programa manipula números negativos corretamente.) (SHRT_MIN pode ser -32768 ou -32767, dependendo do compilador.)
Mas seu professor afirmou explicitamente que n
pode estar no intervalo [-10 ^ 7; 10 ^ 7]. Um número inteiro de 16 bits é muito pequeno; você teria que usar [pelo menos] um número inteiro de 32 bits. Usar int
pode parecer tornar seu código seguro, exceto que int
não é necessariamente um número inteiro de 32 bits. Se você compilar para uma arquitetura de 16 bits, ambos os trechos de código serão falhos. Mas seu código ainda é muito melhor, porque esse cenário reintroduz o bug INT_MIN
mencionado acima com sua versão. Para evitar isso, você pode escrever em long
vez de int
, que é um número inteiro de 32 bits em qualquer arquitetura. A long
é garantido para poder manter qualquer valor no intervalo [-2147483647; 2147483647]. C11 Norma 5.2.4.2.1, mas o valor máximo permitido (sim, máximo, é um número negativo) paraLONG_MIN
é frequentemente-2147483648
LONG_MIN
é 2147483647
.
Que alterações eu faria no seu código?
Seu código está correto, portanto não são realmente reclamações. É mais assim que, se eu realmente precisar dizer algo sobre o seu código, existem algumas pequenas coisas que podem torná-lo um pouco mais claro.
- Os nomes das variáveis podem ser um pouco melhores, mas é uma função curta e fácil de entender, por isso não é grande coisa.
- Você pode alterar a condição de
n
para n!=0
. Semanticamente, é 100% equivalente, mas torna um pouco mais claro.
- Mova a declaração de
c
(à qual renomei digit
) para dentro do loop while, uma vez que é usada apenas lá.
- Altere o tipo de argumento
long
para garantir que ele possa manipular todo o conjunto de entradas.
int sum_of_digits_squared(long n)
{
long sum = 0;
while (n != 0) {
int digit = n % 10;
sum += (digit * digit);
n /= 10;
}
return sum;
}
Na verdade, isso pode ser um pouco enganador, porque - como mencionado acima - a variável digit
pode obter um valor negativo, mas um dígito por si só nunca é positivo ou negativo. Existem algumas maneiras de contornar isso, mas isso é MUITO exigente, e eu não me importaria com detalhes tão pequenos. Especialmente a função separada para o último dígito está levando muito longe. Ironicamente, essa é uma das coisas que o código de seus professores realmente resolve.
- Mude
sum += (digit * digit)
para sum += ((n%10)*(n%10))
e pule a variável digit
completamente.
- Mude o sinal de
digit
se negativo. Mas eu aconselho fortemente a tornar o código mais complexo apenas para fazer sentido o nome de uma variável. É um cheiro muito forte de código.
- Crie uma função separada que extraia o último dígito.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Isso é útil se você quiser usar essa função em outro lugar.
- Apenas nomeie
c
como você faz originalmente. Esse nome de variável não fornece informações úteis, mas, por outro lado, também não é enganoso.
Mas, para ser sincero, nesse ponto você deve seguir para um trabalho mais importante. :)
n = n * (-1)
é uma maneira ridícula de escrevern = -n
; Somente um acadêmico pensaria nisso. Muito menos adicione os parênteses redundantes.