Faut-il éviter <= et> = lors de l'utilisation d'entiers, comme dans une boucle For? [fermé]

15

J'ai expliqué à mes élèves que le test égal à n'est pas fiable pour les variables flottantes, mais convient pour les entiers. Le manuel que j'utilise dit qu'il est plus facile de lire> et <que> = et <=. Je suis d'accord dans une certaine mesure, mais dans une boucle For? N'est-il pas plus clair que la boucle spécifie les valeurs de début et de fin?

Suis-je en train de manquer quelque chose dont l'auteur du manuel a raison?

Un autre exemple est dans les tests de gamme comme:

si score> 89 grade = 'A'
sinon si score> 79 grade = 'B' ...

Pourquoi ne pas simplement dire: si score> = 90?


la source
11
Malheureusement, puisqu'il n'y a pas de différence objective de comportement entre ces options, cela équivaut à un sondage d'opinion sur ce que les gens considèrent comme plus intuitif, et les sondages ne conviennent pas aux sites StackExchange comme celui-ci.
Ixrec
1
Ça n'a pas d'importance. Vraiment.
Auberon
4
En fait, cela est objectivement responsable. Attendez ...
Robert Harvey
9
@Ixrec Je trouve toujours intéressant que les "meilleures pratiques" ne soient pas considérées comme un sujet approprié. Qui ne veut pas améliorer ou rendre son code plus lisible? Si les gens ne sont pas d'accord, nous apprenons tous plus de côtés du problème et nous pourrions ... même ... changer d'avis! Ugh, c'était si difficile à dire. Robert Harvey dit qu'il peut y répondre objectivement. Ce sera intéressant.
1
@nocomprende Surtout parce que «meilleure pratique» est un terme extrêmement vague et galvaudé qui peut renvoyer des conseils utiles basés sur des faits objectifs sur la langue, mais fait tout aussi souvent référence à «l'opinion la plus populaire» (ou l'opinion de celui qui utilise le terme) alors qu'en réalité, toutes les options sont également valides et invalides. Dans ce cas, vous ne pouvez faire un argument objectif qu'en restreignant la réponse à certains types de boucles comme l'a fait Robert, et comme vous l'avez souligné vous-même dans un commentaire qui ne répond pas complètement à la question.
Ixrec

Réponses:

36

Dans les langages de programmation à accolades avec des tableaux à base zéro , il est habituel d'écrire des forboucles comme ceci:

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

Cela traverse tous les éléments du tableau et est de loin le cas le plus courant. Il évite l'utilisation de <=ou >=.

La seule fois que cela devrait changer, c'est quand vous devez ignorer le premier ou le dernier élément, ou le traverser dans la direction opposée, ou le traverser depuis un point de départ différent ou vers un point d'arrivée différent.

Pour les collections, dans les langues qui prennent en charge les itérateurs, il est plus courant de voir ceci:

foreach (var item in list) { }

Ce qui évite complètement les comparaisons.

Si vous cherchez une règle stricte et rapide quant à l'utilisation de <=vs <, il n'y en a pas; utilisez ce qui exprime le mieux votre intention. Si votre code doit exprimer le concept «inférieur ou égal à 55 miles par heure», il doit dire <=non <.

Répondre à votre question sur les gammes de notes est >= 90plus logique, car 90 est la valeur limite réelle, pas 89.

Robert Harvey
la source
9
Il n'évite pas complètement les comparaisons, il les balaie simplement sous le tapis en les déplaçant dans l'implémentation de l'énumérateur. : P
Mason Wheeler
2
Bien sûr, mais cela ne traverse plus un tableau, n'est-ce pas?
Robert Harvey
4
Parce que les tableaux sont le cas d'utilisation le plus courant pour des forboucles comme celle-ci. La forme de forboucle que j'ai fournie ici sera instantanément reconnaissable par tout développeur ayant un minimum d'expérience. Si vous voulez une réponse plus spécifique basée sur un scénario plus spécifique, vous devez l'inclure dans votre question.
Robert Harvey
3
"utilisez ce qui exprime le mieux votre intention" <- Ceci!
Jasper N. Brouwer du
3
@ JasperN.Brouwer C'est comme une loi zéro de la programmation. Toutes les règles de style et les conventions de codage s'effondrent dans la plus grande honte lorsque leurs mandats sont en conflit avec la clarté du code.
Iwillnotexist Idonotexist
16

Ça n'a pas d'importance.

Mais pour le bien de l'argument, nous allons analyser les deux options: a > bvs a >= b.

Attendre! Ce ne sont pas équivalents!
OK, puis a >= b -1vs a > bou a > bvs a >= b +1.

Hm, a >bet les a >= bdeux sont meilleurs que a >= b - 1et a >= b +1. Quels sont tous ces 1s de toute façon? Je dirais donc que tout avantage d'avoir à la >place >=ou vice-versa est éliminé en devant ajouter ou soustraire des 1s aléatoires .

Et si c'est un chiffre? Vaut-il mieux dire a > 7ou a >= 6? Attends une seconde. Sommes-nous sérieusement en train de nous demander s'il est préférable d'utiliser >vs >=et d'ignorer les variables codées en dur? Donc, cela devient vraiment une question de savoir si a > DAYS_OF_WEEKc'est mieux que a >= DAYS_OF_WEEK_MINUS_ONE... ou est-ce a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEvs a >= NUMBER_OF_LEGS_IN_INSECT? Et nous revenons à l'ajout / la soustraction de 1s, mais cette fois dans les noms de variables. Ou peut-être débattre s'il est préférable d'utiliser le seuil, la limite, le maximum.

Et il semble qu'il n'y ait pas de règle générale: cela dépend de ce qui est comparé

Mais vraiment, il y a des choses beaucoup plus importantes à améliorer dans son code et des directives beaucoup plus objectives et raisonnables (par exemple, la limite de caractères X par ligne) qui ont encore des exceptions.

Thanos Tintinidis
la source
1
OK, il n'y a pas de règle générale. (Je me demande pourquoi le manuel semble énoncer une règle générale, mais je peux le laisser aller.) Il n'y a pas de consensus émergent sur le fait que c'est une note que j'ai ratée.
2
@nocomprende parce que les normes de codage et de formatage sont à la fois purement subjectives et très controversées. La plupart du temps, aucun d'eux n'a d'importance, mais les programmeurs mènent toujours des guerres saintes à leur sujet. (ils importent uniquement lorsque le code bâclé est susceptible d'entraîner des bogues)
1
J'ai vu beaucoup de discussions folles sur les normes de codage, mais celle-ci pourrait bien prendre le gâteau. Vraiment idiot.
JimmyJames
@JimmyJames s'agit-il de la discussion de >vs >=ou de la question de savoir si la discussion de >vs >=est significative? bien qu'il soit probablement préférable d'éviter d'en discuter: p
Thanos Tintinidis
2
«Les programmes sont destinés à être lus par les humains et seulement accessoirement aux ordinateurs pour être exécutés» - Donald Knuth. Utilisez le style qui facilite la lecture, la compréhension et la maintenance.
simpleuser
7

Calculativement, il n'y a pas de différence de coût lors de l'utilisation <ou >par rapport à <=ou >=. Il est calculé aussi rapidement.

Cependant, la plupart des boucles compteront à partir de 0 (car de nombreuses langues utilisent l'indexation 0 pour leurs tableaux). Donc, la boucle canonique pour dans ces langues est

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

faire cela avec un <=vous obligerait à ajouter un -1 quelque part pour éviter l'erreur off-by one

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

ou

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

Bien sûr, si le langage utilise une indexation basée sur 1, vous utiliserez <= comme condition limite.

La clé est que les valeurs exprimées dans la condition sont celles de la description du problème. C'est plus propre à lire

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

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

}

pour un intervalle semi-ouvert que

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

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

}

et doivent faire le calcul pour savoir qu'il n'y a pas de valeur possible entre 19 et 20

monstre à cliquet
la source
Je vois l'idée de "longueur", mais que se passe-t-il si vous utilisez simplement des valeurs qui viennent de quelque part et sont destinées à itérer d'une valeur de départ à une valeur de fin. Un exemple de classe était le pourcentage de balisage de 5 à 10. N'est-il pas plus clair de dire: pour (balisage = 5; balisage <= 10; balisage ++) ... ? Pourquoi devrais-je passer à ce test: balisage <11 ?
6
@nocomprende vous ne le feriez pas, si les valeurs limites de votre description de problème sont 5 et 10, alors il vaut mieux les avoir explicitement dans le code plutôt qu'une valeur légèrement modifiée.
ratchet freak
@nocomprende Le format est plus évident lorsque la limite supérieure est un paramètre: for(markup = 5; markup <= MAX_MARKUP; ++markup). Tout le reste serait trop compliqué.
Navin
1

Je dirais que le point n'est pas de savoir si vous devez utiliser> ou> =. Le but est d'utiliser tout ce qui vous permet d'écrire du code expressif.

Si vous trouvez que vous devez ajouter / soustraire un, envisagez d'utiliser l'autre opérateur. Je trouve que de bonnes choses se produisent lorsque vous commencez avec un bon modèle de votre domaine. Ensuite, la logique s'écrit.

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

C'est beaucoup plus expressif que

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

Dans d'autres cas, l'autre voie est préférable:

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

Beaucoup mieux que

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

Veuillez excuser "l'obsession primitive". Évidemment, vous voudriez utiliser respectivement un type Velocity et Money, mais je les ai omis pour des raisons de concision. Le point est le suivant: utilisez une version plus concise et qui vous permet de vous concentrer sur le problème commercial que vous souhaitez résoudre.

Sara
la source
2
L'exemple de limitation de vitesse est un mauvais exemple. Faire 90 km / h dans une zone à 90 km / h n'est pas considéré comme une excès de vitesse. Une limite de vitesse est incluse.
eidsonator
vous avez absolument raison, péter le cerveau de ma part. n'hésitez pas à le modifier pour utiliser un meilleur exemple, j'espère que le point n'est pas complètement perdu.
sara
0

Comme vous l'avez souligné dans votre question, le test d'égalité sur les variables flottantes n'est pas fiable.

Il en va de même pour <=et >=.

Cependant, il n'y a pas un tel problème de fiabilité pour les types entiers. À mon avis, l'auteur exprimait son opinion sur ce qui est plus lisible.

Que vous soyez d'accord ou non avec lui, c'est bien sûr votre opinion.

Dan Pichelman
la source
Est-ce une opinion généralement partagée par d'autres auteurs et des cheveux gris sur le terrain (j'ai les cheveux gris, en fait)? S'il ne s'agit que de "L'opinion d'un journaliste", je peux le dire aux étudiants. Je l'ai, en fait. Jamais entendu parler de cette idée auparavant.
Le sexe de l'auteur n'a pas d'importance, mais j'ai quand même mis à jour ma réponse. Je préfère sélectionner <ou en <=fonction de ce qui est le plus naturel pour le problème particulier que je résous. Comme d'autres l'ont souligné, une boucle FOR a <plus de sens dans les langages de type C. Il existe d'autres cas d'utilisation qui favorisent <=. Utilisez tous les outils à votre disposition, quand et où cela est approprié.
Dan Pichelman
Être en désaccord. Il peut y avoir des problèmes de fiabilité avec les types entiers: en C, considérez: for (unsigned int i = n; i >= 0; i--)ou for (unsigned int i = x; i <= y; i++)si ycela se produit UINT_MAX. Oups, ces boucles pour toujours.
jamesdlin
-1

Chacune des relations <, <=, >=, >et aussi == et !=ont leur cas d' utilisation pour comparer deux valeurs à virgule flottante. Chacun a un sens spécifique et celui qui convient doit être choisi.

Je vais donner des exemples de cas où vous voulez exactement cet opérateur pour chacun d'eux. (Soyez conscient des NaN, cependant.)

  • Vous avez une fonction pure coûteuse fqui prend en entrée une valeur à virgule flottante. Afin d'accélérer vos calculs, vous décidez d'ajouter un cache des valeurs les plus récemment calculées, qui est, un mappage de table de recherche xpour f(x). Vous aurez vraiment besoin d'utiliser ==pour comparer les arguments.
  • Vous voulez savoir si vous pouvez vraiment diviser par un certain nombre x? Vous voulez probablement utiliser x != 0.0.
  • Vous voulez savoir si une valeur xest dans l'intervalle unitaire? (x >= 0.0) && (x < 1.0)est la bonne condition.
  • Vous avez calculé le déterminant dd'une matrice et vous voulez savoir s'il est défini positif? Il n'y a aucune raison d'utiliser autre chose que d > 0.0.
  • Vous voulez savoir si Σ n = 1,…, ∞ n −α diverge? Je testerais alpha <= 1.0.

Les mathématiques à virgule flottante (en général) ne sont pas exactes. Mais cela ne signifie pas que vous devez le craindre, le traiter comme de la magie et certainement pas toujours traiter deux quantités à virgule flottante égales si elles sont à l'intérieur 1.0E-10. Cela cassera vraiment vos calculs et provoquera toutes les choses étranges.

  • Par exemple, si vous utilisez une comparaison floue avec le cache mentionné ci-dessus, vous introduirez des erreurs hilarantes. Mais pire encore, la fonction ne sera plus pure et l'erreur dépendra des résultats précédemment calculés!
  • Oui, même si x != 0.0et yest une valeur à virgule flottante finie, il y / xn'est pas nécessaire qu'elle soit finie. Mais il pourrait être pertinent de savoir si ce y / xn'est pas fini à cause d'un débordement ou parce que l'opération n'était pas mathématiquement bien définie pour commencer.
  • Si vous avez une fonction qui a comme condition préalable qu'un paramètre d'entrée xdoit être dans l'intervalle unitaire [0, 1), je serais vraiment contrarié s'il tirait un échec d'assertion lorsqu'il était appelé avec x == 0.0ou x == 1.0 - 1.0E-14.
  • Le déterminant que vous avez calculé n'est peut-être pas précis. Mais si vous allez prétendre que la matrice n'est pas définie positive lorsque le déterminant calculé l'est 1.0E-30, rien n'est gagné. Tout ce que vous avez fait, c'est augmenter la probabilité de donner la mauvaise réponse.
  • Oui, votre argument alphapeut être affecté par des erreurs d'arrondi et alpha <= 1.0peut donc être vrai même si la véritable valeur mathématique de l'expression a alphaété calculée à partir de peut-être était vraiment supérieure à 1. Mais vous ne pouvez rien faire à ce stade.

Comme toujours en génie logiciel, traitez les erreurs au niveau approprié et ne les gérez qu'une seule fois. Si vous ajoutez des erreurs d'arrondi dans l'ordre de 1.0E-10(cela semble être la valeur magique que la plupart des gens utilisent, je ne sais pas pourquoi) chaque fois que vous comparez des quantités à virgule flottante, vous serez bientôt en erreur dans l'ordre de 1.0E+10

5gon12eder
la source
1
Une excellente réponse, certes, mais pour une question différente de celle posée.
Dewi Morgan
-2

Le type de conditionnel utilisé dans une boucle peut limiter les types d'optimisations qu'un compilateur peut effectuer, pour le meilleur ou pour le pire. Par exemple, étant donné:

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

un compilateur pourrait supposer que la condition ci-dessus devrait entraîner la sortie de la boucle après la nième boucle de passage, à moins que n ne puisse 65535 et que la boucle puisse se terminer d'une autre manière que si i dépasse n. Si ces conditions s'appliquent, le compilateur doit générer du code qui entraînerait l'exécution de la boucle jusqu'à ce que quelque chose d'autre que la condition ci-dessus provoque sa fermeture.

Si la boucle avait plutôt été écrite comme suit:

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

alors un compilateur pourrait supposer en toute sécurité que la boucle n'aurait jamais besoin de s'exécuter plus de n fois et pourrait ainsi générer du code plus efficace.

Notez que tout débordement avec des types signés peut avoir des conséquences désagréables. Donné:

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

Un compilateur pourrait réécrire cela comme:

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;

Une telle boucle se comporterait de manière identique à l'original si aucun débordement ne se produit dans les calculs, mais pourrait s'exécuter indéfiniment, même sur les plates-formes matérielles où le débordement d'entier aurait normalement une sémantique d'encapsulation cohérente.

supercat
la source
Oui, je pense que c'est un peu plus loin dans le manuel. Pas encore une considération. Les optimisations du compilateur sont utiles à comprendre et les débordements doivent être évités, mais ce n'était pas vraiment la motivation de ma question, qui concernait davantage la façon dont les gens comprennent le code. Est-il plus facile de lire x> 5 ou x> = 6? Eh bien, cela dépend ...
À moins que vous n'écriviez pour un Arduino, la valeur maximale pour int sera un peu plus élevée que 65535.
Corey
1
@Corey: La valeur maximale pour uint16_t va être 65535 sur toute plate-forme où le type existe; J'ai utilisé uint16_t dans le premier exemple car je m'attendrais à ce que plus de gens connaissent la valeur maximale exacte d'un uint16_t que celle d'un uint32_t. Le même point s'appliquerait dans les deux cas. Le deuxième exemple est agnostique en ce qui concerne le type de "int", et représente un point de comportement critique que beaucoup de gens ne reconnaissent pas sur les compilateurs modernes.
supercat