Preciso lidar explicitamente com números negativos ou zero ao somar dígitos ao quadrado?

220

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 123caso em que recebo 3 2 1e no -123caso 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.

user010517720
fonte
120
n = n * (-1)é uma maneira ridícula de escrever n = -n; Somente um acadêmico pensaria nisso. Muito menos adicione os parênteses redundantes.
user207421
32
Escreva uma série de testes de unidade para verificar se uma determinada implementação se encaixa na especificação. Se houver um problema (funcional) com um pedaço de código, deve ser possível escrever um teste que demonstre o resultado incorreto, dada uma entrada específica.
Carl
94
Acho interessante que "a soma dos dígitos do número ao quadrado" possa ser interpretada de três (3) maneiras completamente diferentes. (Se o número for 123, as possíveis interpretações renderão 18, 14 e 36.)
Andreas Rejbrand
23
@ilkkachu: "a soma dos dígitos do número ao quadrado". Bem, "o número ao quadrado" é claramente 123 ^ 2 = 15129, então "a soma dos dígitos do número ao quadrado" é "a soma dos dígitos de 15129", que obviamente é 1 + 5 + 1 + 2 + 9 = 18
Andreas Rejbrand 04/10/19
15
n = n * (-1)? Que??? O que seu professor está procurando é o seguinte: `n = -n '. A linguagem C possui um operador menos unário.
Kaz

Respostas:

245

Resumindo uma discussão que tem percolado nos comentários:

  • Não há boas razões para testar antecipadamente n == 0. owhile(n) teste irá lidar com esse caso perfeitamente.
  • É provável que seu professor ainda esteja acostumado a tempos anteriores, quando o resultado de %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) nmesmo quando nnegativo.

Portanto, a resposta para a pergunta "Qual é o significado while(n)?" é "Exatamente o mesmo que while(n != 0)" e a resposta para "Esse código funcionará corretamente tanto para negativos quanto para positivos n?" é "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.

Steve Summit
fonte
39
"Não há boas razões para testar antecipadamente n == 0" - tecnicamente, isso está correto. Mas, como estamos falando de um professor em um ambiente de ensino, eles podem apreciar muito mais a clareza da brevidade do que nós. Adicionar o teste extra por n == 0pelo 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 de sretornado é o correto.
ilkkachu
22
Além disso, o professor pode querer saber que o aluno está ciente e pensou sobre o porquê e como a função se comporta com uma entrada de zero (ou seja, que ela não retorna o valor correto por acidente ). Eles podem ter encontrado alunos que não percebem o que aconteceria nesse caso, que o loop pode executar zero vezes, etc. .. #
224 ilkkachu
36
@ilkkachu Se for esse o caso, o professor deve distribuir uma tarefa que exige que esse teste funcione corretamente.
klutt
38
@ilkkachu Bem, eu entendo o seu argumento no caso geral, porque eu aprecio absolutamente a clareza sobre a brevidade - e praticamente o tempo todo, não necessariamente apenas em um ambiente pedagógico. Mas com isso dito, às vezes a brevidade é a clareza, e se você puder organizar o código principal para lidar com o caso geral e o (s) caso (s) de borda, sem sobrecarregar o código (e a análise de cobertura) com casos especiais para os casos de borda , que coisa linda! Eu acho que é algo para ser apreciado, mesmo no nível iniciante.
Steve Summit
49
@ilkkachu Por esse argumento, você certamente também deve adicionar testes para n = 1 e assim por diante. Não há nada de especial 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.
Voo
107

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 nzero é 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)/bpara todos aeb , 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%bacordo 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==1mas(-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_MINAND 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 npode 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 intpode parecer tornar seu código seguro, exceto que intnã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_MINmencionado acima com sua versão. Para evitar isso, você pode escrever em longvez 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-2147483648LONG_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 npara 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 longpara 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 digitpode 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 digitcompletamente.
  • Mude o sinal de digitse 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 ccomo 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. :)

Klutt
fonte
19
Se a entrada é INT_MINe 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. ;-)
Andrew Henle
5
Deve-se mencionar que, além de (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.)
leftaroundabout
7
@MartinRosenau Você diz "pode". Tem certeza de que isso está realmente acontecendo ou que é permitido pelo padrão ou algo assim ou você está apenas especulando?
klutt
6
@MartinRosenau: Ok, mas usar esses comutadores tornaria o C não mais. O GCC / clang não possui nenhuma chave que interrompa a divisão inteira em qualquer ISA que eu conheço. Embora ignorar o bit de sinal possa dar uma aceleração usando o inverso multiplicativo normal para divisores constantes. (Mas todos os ISAs que eu conheço que possuem uma instrução de divisão de hardware implementam da maneira C99, truncando para zero, para que C %e /operadores possam compilar apenas um idivno x86, ou sdivno 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)
Peter Cordes
5
@TonyK AFIK, é assim que geralmente é resolvido, mas de acordo com o padrão, é UB.
klutt
20

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 * /

Lee Daniel Crocker
fonte
9
Cs %é melhor chamado de restante , porque é isso, mesmo para tipos assinados.
Peter Cordes
15
A quadratura é importante, mas acho que é a parte óbvia. O que deve ser destacado é que (por exemplo) -7 % 10 será realmente, em-7 vez de 3. #
Jacob Raihle
5
"Módulo matemático adequado" não significa nada. O termo correto é "módulo euclidiano" (lembre-se do sufixo!) E é realmente esse o %operador de C não.
Jan Hudec
Gosto dessa resposta porque resolve a questão de várias maneiras de interpretar o módulo. Nunca deixe algo assim ao acaso / interpretação. Isso não é código de golfe.
Harper - Restabelece Monica
11
"o módulo matemático adequado é sempre não negativo" - na verdade não. O resultado de uma operação de módulo é uma classe de equivalência , mas é comum tratar o resultado como o menor número não negativo pertencente a essa classe.
klutt
10

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:

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

Em C ++, isso é basicamente a mesma coisa que :

if (!n) {
        return 0;
}

Isso significa que seu tempo é equivalente a algo assim:

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

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:

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

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 :

O operador binário / produz o quociente e o operador% binário produz o restante da divisão da primeira expressão pela segunda. Se o segundo operando de / ou% for zero, o comportamento será indefinido. Para operandos integrais, o operador / produz o quociente algébrico com qualquer parte fracionária descartada; se o quociente a / b é representável no tipo de resultado, (a / b) * b + a% b é igual a a.

E depois:

Se ambos os operandos não são negativos, o restante é não-negativo; caso contrário, o sinal do restante é definido pela implementação.

Como o pôster da resposta citada corretamente indica, a parte importante desta equação aqui:

(a / b) * b + a% b

Tomando um exemplo do seu caso, você obteria algo como isto:

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

O único problema é a última linha:

Se ambos os operandos não são negativos, o restante é não-negativo; caso contrário, o sinal do restante é definido pela implementação.

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 :

C99 exige que quando a / b é representável:

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

E outro lugar :

Quando números inteiros são divididos e a divisão é inexata, se ambos os operandos forem positivos, o resultado do operador / é o maior número inteiro menor que o quociente algébrico e o resultado do operador% é positivo. Se um dos operandos for negativo, se o resultado do operador / é o maior número inteiro menor que o quociente algébrico ou o menor número inteiro maior que o quociente algébrico é definido pela implementação, como é o sinal do resultado do operador%. Se o quociente a / b for representável, a expressão (a / b) * b + a% b será igual a.

O ANSI C ou o ISO C especificam o que -5% 10 deve ser?

Então sim. Mesmo em C99, isso não parece afetá-lo. A equação é a mesma.

Chipster
fonte
11
As partes que você citou não suportam esta resposta. "o sinal do restante é definido como implementação" não significa que (-1)%10poderia produzir -1ou 1; significa que poderia produzir -1ou 9, e, no último caso (-1)/10, produzirá -1e o código do OP nunca terminará.
stewbasic 4/10/19
Você poderia apontar para uma fonte para isso? Eu tenho muita dificuldade em acreditar que (-1) / 10 é -1. Isso deve ser 0. Além disso, (-1)% 10 = 9 parece violar a equação governante.
Chipster
11
@Chipster, comece com (a/b)*b + a%b == a, depois deixe a=-1; b=10, dando (-1/10)*10 + (-1)%10 == -1. Agora, se de -1/10fato é arredondado para baixo (em direção a -inf), temos (-1/10)*10 == -10, e você precisa que (-1)%10 == 9a 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.
Ilkkachu 04/10/19
11
@Chipster A fonte são os trechos que você citou. Observe que (-1)*10+9=-1, portanto, a escolha (-1)/10=-1e (-1)%10=9não viola a equação governante. Por outro lado, a escolha (-1)%10=1não pode satisfazer a equação governante, não importa como (-1)/10seja escolhida; não existe um número inteiro qtal que q*10+1=-1.
stewbasic 04/10/19
8

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:

if (n < 0) 
    n = -n;

ou pelo menos um comentário:

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

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?

CB
fonte
11
Independentemente de N ser negativo, N ao quadrado será positivo. Então, por que remover o sinal em primeiro lugar? -3 * -3 = 9; 3 * 3 = 9. Ou a matemática mudou nos 30 e poucos anos desde que eu aprendi isso?
Merovex 18/10/19
2
@CB Para ser justo, eu nem percebi que n poderia ser negativo enquanto escrevia o teste, mas quando ele voltou, tive a sensação de que o loop while não seria ignorado, mesmo que o número fosse negativo. Eu fiz alguns testes no meu computador e isso confirmou meu ceticismo. Depois disso, postei esta pergunta. Então não, eu não estava pensando tão profundamente enquanto escrevia o código.
user010517720
5

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 321seria 3.

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 1e 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:

escreve uma função em C / C ++ que retorna a soma dos dígitos do número ao quadrado

Definitivamente, eu teria fornecido duas respostas:

  • a resposta correta (ao quadrado do número primeiro) e
  • a resposta incorreta de acordo com o exemplo, apenas para mantê-lo feliz ;-)
Aprendiz lento
fonte
5
E também um terceiro ao quadrado da soma dos dígitos?
kriss
@kriss - sim, eu não sou tão inteligente :-(
SlowLearner
11
Eu também tive minha parcela de atribuições vagas no meu tempo de estudante. Um professor queria um pouco de biblioteca de manipulação, mas ficou surpreso com o meu código e disse que não estava funcionando. Eu tive que apontar para ele que ele nunca definiu endianness em sua tarefa e ele escolheu o menor número 0 enquanto eu fazia a outra escolha. A única parte irritante é que ele deveria ter sido capaz de descobrir onde estava a diferença sem que eu dissesse a ele.
kriss
1

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.

Paul McCarthy
fonte
2
Programei em C apenas algumas vezes e sei que é ++iincrementado antes da avaliação e i++depois. while(n)também é um recurso de idioma comum. Baseado em uma lógica como essa, já vi muitos códigos como if (foo == TRUE). Eu concordo re: nomes de variáveis, no entanto.
alan Ocallaghan
3
Geralmente, isso não é um mau conselho, mas é excessivo evitar recursos básicos da linguagem (que as pessoas inevitavelmente encontrarão) para fins de programação defensiva. Código curto e claro é geralmente mais legível de qualquer maneira. Não estamos falando de perl ou bash one-liners loucos aqui, apenas recursos de linguagem realmente básicos.
alan Ocallaghan
11
Não tenho certeza, para que foram dados os votos negativos nesta resposta. Para mim, tudo o que é declarado aqui é verdadeiro e importante e um bom conselho para todos os desenvolvedores. Especialmente o smart-ass programação parte, embora while(n)não é o pior exemplo para que (eu "como" a if(strcmp(one, two))mais)
Kai Huppmann
11
Além disso, você realmente não deve deixar ninguém que não saiba a diferença i++e ++imodificar o código C que deve ser usado na produção.
klutt
2
@PaulMcCarthy Somos ambos pela legibilidade do código. Mas discordamos sobre o que isso significa. Além disso, não é objetivo. O que é fácil de ler para uma pessoa pode ser difícil para outra. A personalidade e o conhecimento de fundo afetam fortemente isso. Meu ponto principal é que você não obtém a legibilidade máxima seguindo cegamente algumas regras.
klutt
0

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.

Peter Krassoi
fonte
4
"Retorno extra é uma declaração goto e não usamos goto em C." - é uma combinação de uma generalização muito ampla e um trecho muito distante.
SS Anne
11
"Nós" definitivamente usamos goto em C. Não há nada de errado nisso.
klutt
11
Além disso, não há nada errado com uma função 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
11
@PeterKrassoi Não estou defendendo código difícil de ler, mas já vi vários exemplos em que o código parece uma bagunça completa apenas para evitar um simples goto ou uma declaração de retorno extra. Aqui está um código com o uso apropriado de goto. Eu desafio você a remover as instruções goto e, ao mesmo tempo, tornar o código mais fácil de ler e manter: pastebin.com/aNaGb66Q
klutt
11
@PeterKrassoi Por favor, mostre também a sua versão do seguinte: pastebin.com/qBmR78KA
klutt
0

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:

Escreva uma função no subconjunto comum de C e C ++ que use um número inteiro nno intervalo [-10 7 , 10 7 ] e retorne a soma dos quadrados dos dígitos de sua representação na base 10. Exemplo: se nfor 123, sua função deve retornar 14(1 2 + 2 2 + 3 2 = 14).

A função que você escreveu está correta, exceto por dois detalhes:

  • O argumento deve ter um tipo longpara acomodar todos os valores no intervalo especificado, pois longo 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 tipo inté suficiente para o tipo de retorno, cujo valor máximo é 568.)
  • O comportamento dos %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:

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

A resposta do professor tem várias falhas:

  • O tipo intpode ter um intervalo insuficiente de valores.
  • não há necessidade de caso especial do valor 0.
  • negar valores negativos é desnecessário e pode ter um comportamento indefinido para 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.

chqrlie
fonte