Recentemente, fiz um teste na minha turma. Um dos problemas foi o seguinte:
Dado um número n , escreva uma função em C / C ++ que retorne a soma dos dígitos do número ao quadrado . (O seguinte é importante). O intervalo de n é [- (10 ^ 7), 10 ^ 7]. Exemplo: Se n = 123, sua função retornará 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).
Esta é a função que eu escrevi:
int sum_of_digits_squared(int n)
{
int s = 0, c;
while (n) {
c = n % 10;
s += (c * c);
n /= 10;
}
return s;
}
Parecia certo para mim. Então agora o teste voltou e eu descobri que o professor não me deu todos os pontos por um motivo que eu não entendi. Segundo ele, para que minha função seja concluída, eu deveria ter adicionado os seguintes detalhes:
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;
}
O argumento para isso é que o número n está no intervalo [- (10 ^ 7), 10 ^ 7], portanto pode ser um número negativo. Mas não vejo onde minha própria versão da função falha. Se eu entendi corretamente, o significado de while(n)
é while(n != 0)
, não while (n > 0)
, então na minha versão da função o número n não deixaria de entrar no loop. Funcionaria da mesma forma.
Então, tentei as duas versões da função no meu computador em casa e recebi exatamente as mesmas respostas para todos os exemplos que tentei. Então, sum_of_digits_squared(-123)
é igual a sum_of_digits_squared(123)
(que novamente é igual a 14
) (mesmo sem os detalhes que eu aparentemente deveria ter acrescentado). De fato, se eu tentar imprimir na tela os dígitos do número (do menor ao maior em importância), no 123
caso em que recebo 3 2 1
e no -123
caso em que recebo -3 -2 -1
(o que é realmente interessante). Mas, neste problema, não importaria, uma vez que arredondamos os dígitos.
Então quem está errado?
Edição : Meu mal, eu esqueci de especificar e não sabia que era importante. A versão do C usada em nossa classe e nos testes deve ser C99 ou mais recente . Então eu acho (lendo os comentários) que minha versão obteria a resposta correta de qualquer maneira.
n = n * (-1)
é uma maneira ridícula de escrevern = -n
; Somente um acadêmico pensaria nisso. Muito menos adicione os parênteses redundantes.n = n * (-1)
? Que??? O que seu professor está procurando é o seguinte: `n = -n '. A linguagem C possui um operador menos unário.Respostas:
Resumindo uma discussão que tem percolado nos comentários:
n == 0
. owhile(n)
teste irá lidar com esse caso perfeitamente.%
operandos negativos foi definido de maneira diferente. Em alguns sistemas antigos (incluindo, principalmente, o Unix inicial em um PDP-11, onde Dennis Ritchie originalmente desenvolveu C), o resultado de semprea % b
estava na faixa , o que significa que -123% 10 tinha 7. Nesse sistema, o teste com antecedência seria necessário.[0 .. b-1]
n < 0
Mas o segundo marcador se aplica apenas a épocas anteriores. Nas versões atuais dos padrões C e C ++, a divisão inteira é definida para truncar em direção a 0; portanto,
n % 10
é garantido que você tenha o último dígito (possivelmente negativo)n
mesmo quandon
negativo.Portanto, a resposta para a pergunta "Qual é o significado
while(n)
?" é "Exatamente o mesmo quewhile(n != 0)
" e a resposta para "Esse código funcionará corretamente tanto para negativos quanto para positivosn
?" é "Sim, em qualquer compilador moderno, em conformidade com os padrões". A resposta para a pergunta "Então por que o instrutor anotou?" Provavelmente, eles não estão cientes de uma redefinição significativa de linguagem que aconteceu com o C em 1999 e com o C ++ em 2010, aproximadamente.fonte
n == 0
pelo menos o torna imediatamente e totalmente óbvio para qualquer leitor o que acontece nesse caso. Sem ele, o leitor deve certificar-se de que o loop é realmente ignorado, e o valor padrão des
retornado é o correto.n=0
. A introdução de ramificações e complicações desnecessárias não facilita o código, torna mais difícil, pois agora você não apenas precisa mostrar que o algoritmo geral está correto, mas também precisa pensar em todos os casos especiais separadamente.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 issowhile(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:
A nota de rodapé diz o seguinte:
Truncamento em direção a zero significa que o valor absoluto para
a/b
é igual ao valor absoluto(-a)/b
para todosa
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:
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 linhan = 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. Usarint
pode parecer tornar seu código seguro, exceto queint
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 bugINT_MIN
mencionado acima com sua versão. Para evitar isso, você pode escrever emlong
vez deint
, que é um número inteiro de 32 bits em qualquer arquitetura. Along
é 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.
n
paran!=0
. Semanticamente, é 100% equivalente, mas torna um pouco mais claro.c
(à qual renomeidigit
) para dentro do loop while, uma vez que é usada apenas lá.long
para garantir que ele possa manipular todo o conjunto de entradas.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.sum += (digit * digit)
parasum += ((n%10)*(n%10))
e pule a variáveldigit
completamente.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.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.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. :)
fonte
INT_MIN
e a arquitetura está usando o complemento de dois (o que é muito comum), o código do professor produzirá um comportamento indefinido. Ai. Isso deixará uma marca. ;-)(a/b)*b + a%b ≡ a
, o código do OP também depende do fato de que/
ronda para zero, e isso(-c)*(-c) ≡ c*c
. Ele poderia se argumentar que as verificações extras são justificados apesar padrão garantindo tudo isso, porque é suficientemente não-óbvia. (Claro que poderia igualmente bem ser argumentou que não deveria ser antes um comentário ligando as seções padrão relevantes, mas diretrizes de estilo variar.)%
e/
operadores possam compilar apenas umidiv
no x86, ousdiv
no ARM ou o que for. Ainda assim, isso não tem relação com o código-gen mais rápido para divisores de constante de tempo de compilação)Não gosto completamente da sua versão nem da do seu professor. A versão do seu professor faz os testes extras que você aponta corretamente são desnecessários. O operador de mod de C não é um mod matemático adequado: um número negativo mod 10 produzirá um resultado negativo (o módulo matemático adequado é sempre não negativo). Mas já que você está acertando de qualquer maneira, não há diferença.
Mas isso está longe de ser óbvio, então eu acrescentaria ao seu código não as verificações do seu professor, mas um grande comentário que explica por que ele funciona. Por exemplo:
/ * NOTA: Isso funciona para valores negativos, porque o módulo fica ao quadrado * /
fonte
%
é melhor chamado de restante , porque é isso, mesmo para tipos assinados.-7 % 10
será realmente, em-7
vez de 3. #%
operador de C não.NOTA: Enquanto escrevia esta resposta, você esclareceu que está usando C. A maioria da minha resposta é sobre C ++. No entanto, como seu título ainda possui C ++ e a pergunta ainda está marcada como C ++, decidi responder de qualquer maneira, caso isso ainda seja útil para outras pessoas, principalmente porque a maioria das respostas que eu vi até agora não é satisfatória.
Na moderna C ++ (Nota: eu realmente não sei onde C está), seu professor parece estar errado em ambos os aspectos.
Primeiro é esta parte aqui:
Em C ++, isso é basicamente a mesma coisa que :
Isso significa que seu tempo é equivalente a algo assim:
Isso significa que, como você está apenas saindo do seu if quando o tempo não seria executado de qualquer maneira, realmente não há uma razão para colocar isso se aqui, pois o que você está fazendo após o loop e no if é equivalente de qualquer maneira. Embora eu deva dizer que, por alguma razão, essas eram diferentes, você precisaria disso se.
Realmente, essa declaração if não é particularmente útil, a menos que eu esteja enganado.
A segunda parte é onde as coisas ficam peludas:
O cerne da questão é qual é a saída do módulo de um número negativo.
No C ++ moderno, isso parece estar bem definido :
E depois:
Como o pôster da resposta citada corretamente indica, a parte importante desta equação aqui:
Tomando um exemplo do seu caso, você obteria algo como isto:
O único problema é a última linha:
Isso significa que, em um caso como esse, apenas o sinal parece estar definido para implementação. Isso não deve ser um problema no seu caso porque, porque você está elevando esse valor de qualquer maneira.
Dito isto, lembre-se de que isso não se aplica necessariamente a versões anteriores do C ++ ou C99. Se é isso que seu professor está usando, talvez seja por isso.
EDIT: Não, eu estou errado. Esse também parece ser o caso do C99 ou posterior :
E outro lugar :
Então sim. Mesmo em C99, isso não parece afetá-lo. A equação é a mesma.
fonte
(-1)%10
poderia produzir-1
ou1
; significa que poderia produzir-1
ou9
, e, no último caso(-1)/10
, produzirá-1
e o código do OP nunca terminará.(a/b)*b + a%b == a
, depois deixea=-1; b=10
, dando(-1/10)*10 + (-1)%10 == -1
. Agora, se de-1/10
fato é arredondado para baixo (em direção a -inf), temos(-1/10)*10 == -10
, e você precisa que(-1)%10 == 9
a primeira equação corresponda. Como as demais respostas , não é assim que funciona nos padrões atuais, mas é como costumava funcionar. Não é realmente sobre o sinal do restante como tal, mas como a divisão é arredondada e o que o restante deve ser para satisfazer a equação.(-1)*10+9=-1
, portanto, a escolha(-1)/10=-1
e(-1)%10=9
não viola a equação governante. Por outro lado, a escolha(-1)%10=1
não pode satisfazer a equação governante, não importa como(-1)/10
seja escolhida; não existe um número inteiroq
tal queq*10+1=-1
.Como outros já apontaram, o tratamento especial para n == 0 não faz sentido, pois para todo programador sério de C é óbvio que "while (n)" faz o trabalho.
O comportamento para n <0 não é tão óbvio, é por isso que eu preferiria ver essas duas linhas de código:
ou pelo menos um comentário:
Honestamente, a que horas você começou a considerar que n pode ser negativo? Ao escrever o código ou ao ler as observações do seu professor?
fonte
Isso me lembra uma tarefa que eu falhei
Nos anos 90. O palestrante estava brotando sobre loops e, resumindo, nossa tarefa era escrever uma função que retornasse o número de dígitos para qualquer número inteiro> 0.
Então, por exemplo, o número de dígitos
321
seria3
.Embora a tarefa simplesmente dissesse escrever uma função que retornasse o número de dígitos, a expectativa era que usássemos um loop que se divide por 10 até ... você a entende, conforme abordado na palestra .
Mas o uso de loops não foi explicitamente declarado, então eu:
took the log, stripped away the decimals, added 1
e foi subseqüentemente criticado na frente de toda a classe.A questão é que o objetivo da tarefa era testar nossa compreensão do que aprendemos durante as palestras . Na palestra que recebi, aprendi que o professor de informática era um idiota (mas talvez um idiota com um plano?)
Na sua situação:
Definitivamente, eu teria fornecido duas respostas:
fonte
Geralmente nas tarefas, nem todas as marcas são atribuídas apenas porque o código funciona. Você também recebe marcas para facilitar a leitura, a eficiência e a elegância da solução. Essas coisas nem sempre são mutuamente exclusivas.
Uma coisa que eu não posso dizer o suficiente é "usar nomes de variáveis significativos" .
No seu exemplo, não faz muita diferença, mas se você estiver trabalhando em um projeto com milhões de linhas de legibilidade de código, isso se tornará muito importante.
Outra coisa que costumo ver com o código C são as pessoas que tentam parecer inteligentes. Em vez de usar while (n! = 0) , mostrarei a todos como sou inteligente escrevendo enquanto (n) porque significa a mesma coisa. Bem, isso acontece no compilador, mas, como você sugeriu, a versão mais antiga do professor não a implementou da mesma maneira.
Um exemplo comum é referenciar um índice em uma matriz e incrementá-lo ao mesmo tempo; Números [i ++] = iPrime;
Agora, o próximo programador que trabalha com o código precisa saber se eu sou incrementado antes ou depois da tarefa, apenas para que alguém possa se exibir.
Um megabyte de espaço em disco é mais barato que um rolo de papel higiênico, procure mais clareza do que tentar economizar espaço; seus colegas programadores ficarão mais felizes.
fonte
++i
incrementado antes da avaliação ei++
depois.while(n)
também é um recurso de idioma comum. Baseado em uma lógica como essa, já vi muitos códigos comoif (foo == TRUE)
. Eu concordo re: nomes de variáveis, no entanto.while(n)
não é o pior exemplo para que (eu "como" aif(strcmp(one, two))
mais)i++
e++i
modificar o código C que deve ser usado na produção.Eu não discutiria se a definição original ou moderna de '%' é melhor, mas quem escreve duas declarações de retorno em uma função tão curta não deve ensinar programação C. Retorno extra é uma instrução goto e não usamos goto em C. Além disso, o código sem a verificação zero teria o mesmo resultado, o retorno extra dificultou a leitura.
fonte
int findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
A declaração do problema é confusa, mas o exemplo numérico esclarece o significado da soma dos dígitos do número ao quadrado . Aqui está uma versão melhorada:
A função que você escreveu está correta, exceto por dois detalhes:
long
para acomodar todos os valores no intervalo especificado, poislong
o padrão C garante que o tipo tenha pelo menos 31 bits de valor, portanto, um intervalo suficiente para representar todos os valores em [-10 7 , 10 7 ] . (Observe que o tipoint
é suficiente para o tipo de retorno, cujo valor máximo é568
.)%
operandos negativos não é intuitivo e sua especificação variou entre o C99 Standard e as edições anteriores. Você deve documentar por que sua abordagem é válida mesmo para entradas negativas.Aqui está uma versão modificada:
A resposta do professor tem várias falhas:
int
pode ter um intervalo insuficiente de valores.0
.n = INT_MIN
.Dadas as restrições adicionais na declaração do problema (C99 e intervalo de valores para
n
), apenas a primeira falha é um problema. O código extra ainda produz as respostas corretas.Você deve obter uma boa nota neste teste, mas a explicação é necessária em um teste escrito para mostrar sua compreensão dos problemas como negativos
n
; caso contrário, o professor pode assumir que você não tinha conhecimento e teve sorte. Em uma prova oral, você teria recebido uma pergunta e sua resposta o teria acertado.fonte