<= E> = devem ser evitados ao usar números inteiros, como em um loop For? [fechadas]


15

Expliquei aos meus alunos que o teste igual a não é confiável para variáveis ​​de flutuação, mas é bom para números inteiros. O livro que estou usando disse que é mais fácil ler> e <que> = e <=. Eu concordo até certo ponto, mas em um loop For? Não é mais claro que o loop especifique os valores inicial e final?

Estou perdendo algo que o autor do livro está correto?

Outro exemplo está nos testes de alcance, como:

se pontuação> 89 nota = 'A'
senão se pontuação> 79 nota = 'B' ...

Por que não dizer apenas: se pontuação> = 90?

loops 

11
Infelizmente, como não há diferença objetiva no comportamento entre essas opções, isso equivale a uma pesquisa de opinião sobre o que as pessoas consideram mais intuitivas, e as pesquisas não são adequadas para sites StackExchange como este.
Ixrec 18/03/16

1
Não importa. Realmente.
Auberon

4
Na verdade, isso é objetivamente responsável. Suporte ...
Robert Harvey

9
@Ixrec Eu sempre acho interessante que a "melhor prática" não seja considerada um tópico adequado. Quem não quer melhorar ou tornar seu código mais legível? Se as pessoas discordam, todos aprendemos mais lados da questão e podemos ... até ... mudar de idéia! Ugh, isso foi tão difícil de dizer. Robert Harvey diz que pode responder objetivamente. Isso será interessante.

1
@nocomprende Principalmente porque "melhores práticas" é um termo extremamente vago e usado em excesso, que pode indicar conselhos úteis com base em fatos objetivos sobre o idioma, mas com a mesma frequência se refere à "opinião mais popular" (ou à opinião de quem está usando o termo) quando, na realidade, todas as opções são igualmente válidas e inválidas. Nesse caso, você só pode argumentar objetivamente restringindo a resposta a certos tipos de loops, como Robert fez, e como você se destacou em um comentário que não responde completamente à pergunta.
Ixrec 18/03/16

Respostas:


36

Em linguagens de programação com chaves baseadas em zero , é habitual escreverfor loops como este:

for (int i = 0; i < array.Length, i++) { }

Isso percorre todos os elementos da matriz e é de longe o caso mais comum. Evita o uso de<= ou >=.

O único momento em que isso precisaria mudar é quando você precisa pular o primeiro ou o último elemento, atravessá-lo na direção oposta ou atravessá-lo de um ponto inicial diferente ou de um ponto final diferente.

Para coleções, em idiomas que suportam iteradores, é mais comum ver o seguinte:

foreach (var item in list) { }

O que evita totalmente as comparações.

Se você está procurando uma regra rígida e rápida sobre quando usar o <=vs <, não há uma; use o que melhor expressa sua intenção. Se seu código precisar expressar o conceito "Menor ou igual a 55 milhas por hora", será necessário dizer<= não <.

Para responder sua pergunta sobre os intervalos de notas, >= 90faz mais sentido, porque 90 é o valor limite real, não 89.


9
Ele não evita completamente as comparações, apenas as varre para baixo do tapete, movendo-as para a implementação do enumerador. : P
Mason Wheeler

2
Claro, mas isso não é mais atravessar uma matriz, é?
Robert Harvey

4
Porque matrizes são o caso de uso mais comum para forloops como esse. A forma de forloop que forneci aqui será instantaneamente reconhecível por qualquer desenvolvedor com um pouco de experiência. Se você deseja uma resposta mais específica com base em um cenário mais específico, inclua-a na sua pergunta.
Robert Harvey

3
"use o que melhor expressa sua intenção" <- Isto!
Jasper N. Brouwer

3
@ JasperN.Brouwer É como uma lei zero da programação. Todas as regras de estilo e convenções de codificação entram em colapso mais profundamente quando seus mandatos entram em conflito com a clareza do código.
Iwillnotexist Idonotexist

16

Não importa.

Mas, para o bem do argumento, vamos analisar as duas opções: a > bvs a >= b.

Espere! Aqueles não são equivalentes!
OK, então a >= b -1vs a > bou a > bvs a >= b +1.

Hum, a >be a >= bambos parecem melhores que a >= b - 1e a >= b +1. O que são todos esses 1s afinal? Então, eu argumentaria que qualquer benefício de ter em >vez de >=ou vice-versa é eliminado pela necessidade de adicionar ou subtrair 1s aleatórios .

Mas e se for um número? É melhor dizer a > 7ou a >= 6? Espere um segundo. Estamos discutindo seriamente se é melhor usar >vs >=e ignorar as variáveis ​​codificadas? Então, torna-se realmente uma questão de saber se a > DAYS_OF_WEEKé melhor do que a >= DAYS_OF_WEEK_MINUS_ONE... ou é a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEvs a >= NUMBER_OF_LEGS_IN_INSECT? E voltamos a adicionar / subtrair 1s, apenas desta vez em nomes de variáveis. Ou talvez debatendo se é melhor usar limiar, limite, máximo.

E parece que não há regra geral: depende do que está sendo comparado

Mas, realmente, há coisas muito mais importantes a serem aprimoradas no código de alguém e diretrizes muito mais objetivas e razoáveis ​​(por exemplo, limite de caracteres X por linha) que ainda têm exceções.


1
OK, não há regra geral. (Imaginando por que o livro parecia declarar uma regra geral, mas posso deixar para lá.) Não há consenso emergente de que esse foi um memorando que eu perdi.

2
@nocomprende porque os padrões de codificação e formatação são puramente subjetivos e altamente controversos. Na maioria das vezes, nenhum deles importa, mas os programadores ainda fazem guerras sagradas sobre eles. (eles só importa quando o código desleixado é provável que resulte em erros)

1
Eu já vi muitas discussões malucas sobre padrões de codificação, mas essa pode ser a melhor opção. Muito bobo.
18716 JimmyJames

@JimmyJames é sobre a discussão de >vs >=ou se a discussão de >vs >=é significativa? embora seja provavelmente melhor para evitar discutir isto: p
Thanos Tintinidis

2
“Os programas são feitos para serem lidos por seres humanos e apenas incidentalmente para os computadores executarem” - Donald Knuth. Use o estilo que facilita a leitura, a compreensão e a manutenção.
simpleuser

7

Computacionalmente, não há diferença de custo ao usar <ou >comparar com <=ou >=. É calculado igualmente rápido.

No entanto, a maioria dos loops será contada a partir de 0 (porque muitos idiomas usam a indexação 0 para suas matrizes). Portanto, o loop for canônico nessas línguas é

for(int i = 0; i < length; i++){
   array[i] = //...
   //...
}

fazer isso com a <=exigiria que você adicionasse -1 em algum lugar para evitar o erro de desvio

for(int i = 1; i <= length; i++){
   array[i-1] = //...
   //...
}

ou

for(int i = 0; i <= length-1; i++){
   array[i] = //...
   //...
}

Obviamente, se o idioma usar indexação baseada em 1, você usaria <= como condição limitadora.

A chave é que os valores expressos na condição são os da descrição do problema. É mais limpo ler

if(x >= 10 && x < 20){

} else if(x >= 20 && x < 30){

}

por um intervalo semiaberto que

if(x >= 10 && x <= 19){

} else if(x >= 20 && x <= 29){

}

e precisa fazer as contas para saber que não há valor possível entre 19 e 20


Eu vejo a idéia do "comprimento", mas e se você estiver apenas usando valores que vieram de algum lugar e pretendem iterar de um valor inicial para um valor final. Um exemplo de classe foi a porcentagem de marcação de 5 a 10. Não é mais claro dizer: para (marcação = 5; marcação <= 10; marcação ++) ... ? Por que eu mudaria para o teste: marcação <11 ?

6
@nocomprende você não faria isso, se os valores limite da descrição do problema forem 5 e 10, é melhor tê-los explicitamente no código, em vez de um valor ligeiramente alterado.
catraca aberração

@nocomprende O formato correto é mais evidente quando o limite superior é um parâmetro: for(markup = 5; markup <= MAX_MARKUP; ++markup). Qualquer outra coisa seria complicar demais.
Navin

1

Eu diria que o ponto não é se você deve usar> ou> =. O objetivo é usar o que lhe permite escrever código expressivo.

Se você achar que precisa adicionar / subtrair um, considere usar o outro operador. Acho que coisas boas acontecem quando você começa com um bom modelo de seu domínio. Então a lógica se escreve.

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour > speedLimit;
}

Isso é muito mais expressivo do que

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour >= (speedLimit + 1);
}

Noutros casos, é preferível o contrário:

bool CanAfford(decimal price, decimal balance)
{
    return balance >= price;
}

Muito melhor que

bool CanAfford(decimal price, decimal balance)
{
    const decimal epsilon = 0e-10m;
    return balance > (price - epsilon);
}

Por favor, desculpe a "obsessão primitiva". Obviamente, você gostaria de usar os tipos Velocity e Money aqui, respectivamente, mas eu os omiti por brevidade. O ponto é: use a versão mais concisa e que permita que você se concentre no problema de negócios que deseja resolver.


2
Exemplo de limite de velocidade é um exemplo ruim. Passar 90 km / h em uma zona de 90 km / h não é considerado excesso de velocidade. Um limite de velocidade é inclusivo.
eidsonator

você está absolutamente correto, peido cerebral da minha parte. fique à vontade para editá-lo para usar um exemplo melhor, espero que o ponto não esteja completamente perdido.
Sara

0

Como você apontou na sua pergunta, o teste de igualdade em variáveis ​​flutuantes não é confiável.

O mesmo vale para <=e >=.

No entanto, não existe esse problema de confiabilidade para tipos inteiros. Na minha opinião, o autor estava expressando sua opinião sobre qual é mais legível.

Se você concorda ou não com ele, é claro que é sua opinião.


Essa é uma visão geralmente aceita por outros autores e cabelos grisalhos no campo (eu tenho cabelos grisalhos, na verdade)? Se for apenas a "Opinião de um repórter", posso dizer aos alunos. Eu tenho, na verdade. Nunca ouvi falar dessa idéia antes.

O sexo do autor é irrelevante, mas atualizei minha resposta de qualquer maneira. Prefiro selecionar <ou com <=base no que é mais natural para o problema específico que estou resolvendo. Como outros já apontaram, em um loop FOR <faz mais sentido em linguagens do tipo C. Existem outros casos de uso que favorecem <=. Use todas as ferramentas à sua disposição, quando e onde apropriado.
Dan Pichelman

Discordo. Não pode haver problemas de confiabilidade com os tipos inteiros: em C, considerar: for (unsigned int i = n; i >= 0; i--)ou for (unsigned int i = x; i <= y; i++)se yacontece de ser UINT_MAX. Opa, esses loop para sempre.
jamesdlin

-1

Cada uma das relações <, <=, >=, >e também == e !=têm seus casos de uso para comparar dois valores de ponto flutuante. Cada um tem um significado específico e o apropriado deve ser escolhido.

Vou dar exemplos de casos em que você deseja exatamente esse operador para cada um deles. (Esteja ciente dos NaNs, no entanto.)

  • Você tem uma função pura e dispendiosa fque assume um valor de ponto flutuante como entrada. Para acelerar seus cálculos, você decide adicionar um cache dos valores computados mais recentemente, ou seja, um mapeamento de tabela de pesquisa xpara f(x). Você realmente deseja usar ==para comparar os argumentos.
  • Você quer saber se pode dividir significativamente por algum número x? Você provavelmente quer usar x != 0.0.
  • Você quer saber se um valor xestá no intervalo da unidade? (x >= 0.0) && (x < 1.0)é a condição correta.
  • Você calculou o determinante dde uma matriz e deseja saber se é definitivo positivo? Não há razão para usar qualquer outra coisa, exceto d > 0.0.
  • Você quer saber se = n = 1,…, ∞ n diverge? Eu testaria alpha <= 1.0.

A matemática de ponto flutuante (em geral) não é exata. Mas isso não significa que você deve temê-lo, tratá-lo como mágica e, certamente, nem sempre tratar duas quantidades de ponto flutuante iguais se estiverem dentro 1.0E-10. Fazer isso realmente quebrará sua matemática e fará com que todas as coisas estranhas aconteçam.

  • Por exemplo, se você usar a comparação difusa com o cache mencionado acima, apresentará erros hilariantes. Pior ainda, a função não será mais pura e o erro dependerá dos resultados calculados anteriormente!
  • Sim, mesmo que x != 0.0e yseja um valor finito de ponto flutuante, y / xnão precisa ser finito. Mas pode ser relevante saber se y / xnão é finito por causa do estouro ou porque a operação não foi matematicamente bem definida para começar.
  • Se você tem uma função que tem como pré-condição o parâmetro de entrada xno intervalo de unidades [0, 1), ficaria muito chateado se disparasse uma falha de asserção quando chamado com x == 0.0ou x == 1.0 - 1.0E-14.
  • O determinante que você calculou pode não ser preciso. Mas se você pretende fingir que a matriz não é positiva definida quando o determinante calculado é 1.0E-30, nada é ganho. Tudo o que você fez foi aumentar a probabilidade de dar a resposta errada.
  • Sim, seu argumento alphapode ser afetado por erros de arredondamento e, portanto, alpha <= 1.0pode ser verdadeiro, mesmo que o verdadeiro valor matemático para a expressão tenha alphasido calculado possa ter sido realmente maior que 1. Mas não há nada que você possa fazer sobre isso neste momento.

Como sempre na engenharia de software, lide com os erros no nível apropriado e lide com eles apenas uma vez. Se você adicionar erros de arredondamento na ordem de 1.0E-10(este parece ser o valor mágico que a maioria das pessoas usa, não sei por quê) toda vez que comparar quantidades de ponto flutuante, em breve você encontrará erros na ordem de 1.0E+10...


1
Uma resposta excelente, embora admitidamente, apenas para uma pergunta diferente da que foi feita.
Dewi Morgan

-2

O tipo de condicional usado em um loop pode limitar os tipos de otimizações que um compilador pode executar, para melhor ou para pior. Por exemplo, dado:

uint16_t n = ...;
for (uint16_t i=1; i<=n; i++)
  ...  [loop doesn't modify i]

um compilador pode assumir que a condição acima deve fazer com que o loop saia após o enésimo enésimo passo, a menos que n possa 65535 e o loop possa sair de alguma outra maneira que não seja i excedendo n. Se essas condições se aplicarem, o compilador deverá gerar um código que faça com que o loop seja executado até que algo diferente da condição acima faça com que ele saia.

Se o loop tivesse sido escrito como:

uint16_t n = ...;
for (uint16_t ctr=0; ctr<n; ctr++)
{
  uint16_t i = ctr+1;
  ... [loop doesn't modify ctr]
}

então, um compilador poderia assumir com segurança que o loop nunca precisaria ser executado mais de n vezes e, portanto, seria capaz de gerar código mais eficiente.

Observe que qualquer estouro com tipos assinados pode ter consequências desagradáveis. Dado:

int total=0;
int start,lim,mult; // Initialize values somehow...
for (int i=start; i<=lim; i++)
  total+=i*mult;

Um compilador pode reescrever isso como:

int total=0;
int start,lim,mult; // Initialize values somehow...
int loop_top = lim*mult;
for (int i=start; i<=loop_top; i+=mult)
  total+=i;

Esse loop se comportaria de forma idêntica ao original se não ocorrer um estouro nos cálculos, mas poderá ser executado para sempre, mesmo em plataformas de hardware onde o excesso de números normalmente teria semântica de empacotamento consistente.


Sim, acho que isso é um pouco mais distante no livro. Ainda não é uma consideração. As otimizações do compilador são úteis para entender, e o excesso deve ser evitado, mas não era realmente a motivação para minha pergunta, que era mais em termos de como as pessoas entendem o código. É mais fácil ler x> 5 ou x> = 6? Bem, isso depende ... #

A menos que você está escrevendo para um Arduino o valor máximo para int vai ser um pouco maior do que 65535.
Corey

1
@ Corey: o valor máximo para uint16_t será 65535 em qualquer plataforma em que o tipo exista; Eu usei uint16_t no primeiro exemplo, porque esperaria que mais pessoas soubessem o valor máximo exato de um uint16_t do que o de um uint32_t. O mesmo ponto se aplicaria em ambos os casos. O segundo exemplo é agnóstico em relação ao tipo de "int" e representa um ponto comportamental crítico que muitas pessoas deixam de reconhecer sobre os compiladores modernos.
Supercat
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.