Sollten <= und> = vermieden werden, wenn Ganzzahlen verwendet werden, z. B. in einer For-Schleife? [geschlossen]

15

Ich habe meinen Schülern erklärt, dass Gleichheitstests für Float-Variablen nicht zuverlässig sind, aber für ganze Zahlen in Ordnung sind. Das Lehrbuch, das ich benutze, besagt, dass es einfacher ist,> und <als> = und <= zu lesen. Ich stimme einigermaßen zu, aber in einer For-Schleife? Ist es nicht klarer, wenn die Schleife den Start- und den Endwert angibt?

Vermisse ich etwas, worüber der Autor des Lehrbuchs korrekt ist?

Ein weiteres Beispiel sind Reichweitentests wie:

wenn Punktzahl> 89 Note = 'A'
sonst wenn Punktzahl> 79 Note = 'B' ...

Warum nicht einfach sagen: wenn Score> = 90?


quelle
11
Da es keinen objektiven Unterschied im Verhalten zwischen diesen Optionen gibt, handelt es sich um eine Meinungsumfrage darüber, was die Benutzer für intuitiver halten, und Umfragen eignen sich nicht für StackExchange-Sites wie diese.
Ixrec
1
Es spielt keine Rolle. Ja wirklich.
Auberon
4
Eigentlich ist dies objektiv zu verantworten. Stand by ...
Robert Harvey
9
@Ixrec Ich finde es immer interessant, dass "Best Practice" kein geeignetes Thema ist. Wer möchte seinen Code nicht verbessern oder besser lesbar machen? Wenn die Leute nicht einverstanden sind, lernen wir alle mehr Seiten des Problems und können ... sogar ... unsere Meinung ändern! Das war so schwer zu sagen. Robert Harvey sagt, dass er es objektiv beantworten kann. Das wird interessant sein.
1
@nocomprende Vor allem , weil „best practice“ ist eine äußerst vage und überstrapaziert Begriff, kann nützliche Ratschläge auf der Grundlage objektiver Fakten über die Sprache beziehen, aber ebenso oft bezieht sich auf die „populärste Meinung“ (oder der Meinung , wer ist mit dem Begriff) In Wirklichkeit sind alle Optionen gleichermaßen gültig und ungültig. In diesem Fall können Sie nur ein objektives Argument vorbringen, indem Sie die Antwort auf bestimmte Arten von Schleifen beschränken, wie dies Robert getan hat, und wie Sie selbst in einem Kommentar betont haben, der die Frage nicht vollständig beantwortet.
Ixrec

Antworten:

36

In geschweiften Programmiersprachen mit nullbasierten Arrays ist es üblich, forSchleifen wie folgt zu schreiben :

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

Dies durchläuft alle Elemente im Array und ist bei weitem der häufigste Fall. Es vermeidet die Verwendung von <=oder >=.

Dies müsste sich nur ändern, wenn Sie das erste oder letzte Element überspringen oder in die entgegengesetzte Richtung oder von einem anderen Startpunkt zu einem anderen Endpunkt bewegen müssen.

Für Sammlungen in Sprachen, die Iteratoren unterstützen, ist Folgendes üblicher:

foreach (var item in list) { }

Das vermeidet die Vergleiche gänzlich.

Wenn Sie nach einer festen Regel für die Verwendung von <=vs suchen <, gibt es keine. Verwenden Sie, was Ihre Absicht am besten ausdrückt. Wenn Ihr Code das Konzept "Weniger als oder gleich 55 Meilen pro Stunde" ausdrücken muss, <=muss dies nicht heißen <.

Die Beantwortung Ihrer Frage zu den Notenbereichen >= 90ist sinnvoller, da 90 der tatsächliche Grenzwert ist und nicht 89.

Robert Harvey
quelle
9
Die Vergleiche werden nicht gänzlich vermieden, sondern nur unter den Teppich gekehrt, indem sie in die Enumerator-Implementierung verschoben werden. : P
Mason Wheeler
2
Natürlich, aber das durchquert kein Array mehr, oder?
Robert Harvey
4
Weil Arrays der häufigste Anwendungsfall für forsolche Schleifen sind. Die Form der forSchleife, die ich hier zur Verfügung gestellt habe, ist für jeden Entwickler mit ein wenig Erfahrung sofort erkennbar. Wenn Sie eine spezifischere Antwort basierend auf einem spezifischeren Szenario wünschen, müssen Sie dies in Ihre Frage aufnehmen.
Robert Harvey
3
"benutze, was deine Absicht am besten ausdrückt" <- This!
Jasper N. Brouwer
3
@ JasperN.Brouwer Es ist wie ein nulltes Gesetz der Programmierung. Alle Stilregeln und Codierungskonventionen brechen in größter Schande zusammen, wenn ihre Mandate der Klarheit des Codes widersprechen.
Ich werde nicht existieren Idonotexist
16

Es spielt keine Rolle.

Aber aus Gründen der Argumentation, lassen Sie sich die beiden Optionen analysiert: a > bvs a >= b.

Abwarten! Das sind nicht gleichbedeutend!
OK, dann a >= b -1vs a > boder a > bvs a >= b +1.

Hm, a >bund a >= bbeide sehen besser aus als a >= b - 1und a >= b +1. Was sind das für alles 1? Also würde ich , dass jeder Vorteil argumentieren aus, die >statt >=durch, die addieren oder subtrahieren Zufalls- oder umgekehrt eliminiert 1s.

Aber was ist, wenn es eine Zahl ist? Ist es besser zu sagen a > 7oder a >= 6? Warte eine Sekunde. Überlegen wir ernsthaft, ob es besser ist , die hartcodierten Variablen mit >vs zu vergleichen >=und zu ignorieren? Es wird also wirklich eine Frage, ob a > DAYS_OF_WEEKes besser ist als a >= DAYS_OF_WEEK_MINUS_ONE... oder ob es a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEvs ist a >= NUMBER_OF_LEGS_IN_INSECT? Und wir werden wieder 1s addieren / subtrahieren , nur diesmal in Variablennamen. Oder vielleicht darüber nachdenken, ob es am besten ist, Schwelle, Grenze, Maximum zu verwenden.

Und es sieht so aus, als gäbe es keine allgemeine Regel: Es kommt darauf an, was verglichen wird

In der Tat gibt es viel wichtigere Dinge, die im eigenen Code verbessert werden müssen, und viel objektivere und vernünftigere Richtlinien (z. B. Begrenzung der X-Zeichen pro Zeile), für die es noch Ausnahmen gibt.

Thanos Tintinidis
quelle
1
OK, es gibt keine allgemeine Regel. (Ich frage mich, warum das Lehrbuch eine allgemeine Regel zu enthalten schien, aber ich kann es loslassen.) Es gibt keinen Konsens darüber, dass es ein Memo war, das ich verpasst habe.
2
@nocomprende, weil Codierungs- und Formatierungsstandards sowohl rein subjektiv als auch höchst umstritten sind. Meistens spielt keiner von ihnen eine Rolle, aber Programmierer führen immer noch heilige Kriege über sie. (Sie spielen nur eine Rolle, wenn schlampiger Code wahrscheinlich zu Fehlern führt.)
1
Ich habe eine Menge verrückter Diskussionen über Codierungsstandards gesehen, aber dieser könnte einfach die Nase voll haben. Wirklich albern.
JimmyJames
@ JimmyJames ist dies über die Diskussion von >vs >=oder die Diskussion darüber, ob die Diskussion von >vs >=sinnvoll ist? obwohl es wahrscheinlich am besten ist, dies nicht zu diskutieren: p
Thanos Tintinidis
2
"Programme sollen vom Menschen gelesen werden und nur nebenbei von Computern ausgeführt werden" - Donald Knuth. Verwenden Sie den Stil, der das Lesen, Verstehen und Pflegen am einfachsten macht.
Simpleuser
7

Rechnerisch gibt es keinen Unterschied in den Kosten bei der Verwendung <oder im >Vergleich zu <=oder >=. Es wird genauso schnell berechnet.

Die meisten for-Schleifen werden jedoch von 0 an gezählt (da viele Sprachen für ihre Arrays die Indexierung 0 verwenden). Die kanonische for-Schleife in diesen Sprachen lautet also

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

Wenn Sie dies mit einem tun, <=müssen Sie irgendwo ein -1 hinzufügen, um den Fehler zu vermeiden, bei dem die Abweichung um eins auftritt

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

oder

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

Wenn die Sprache eine 1-basierte Indizierung verwendet, würden Sie natürlich <= als einschränkende Bedingung verwenden.

Der Schlüssel ist, dass die in der Bedingung ausgedrückten Werte diejenigen aus der Problembeschreibung sind. Es ist sauberer zu lesen

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

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

}

für ein halboffenes Intervall als

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

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

}

und muss rechnen, um zu wissen, dass es keinen möglichen Wert zwischen 19 und 20 gibt

Ratschenfreak
quelle
Ich sehe die Idee "Länge", aber was ist, wenn Sie nur Werte verwenden, die von irgendwoher kommen und von einem Startwert zu einem Endwert iterieren sollen. Ein Klassenbeispiel war Markup-Prozentsatz von 5 bis 10. Ist es nicht klarer zu sagen: für (markup = 5; markup <= 10; markup ++) ... ? Warum sollte ich zum Test wechseln: markup <11 ?
6
@nocomprende würdest du nicht, wenn die Grenzwerte aus deiner Problembeschreibung 5 und 10 sind, dann ist es besser, sie explizit im Code zu haben, als einen leicht geänderten Wert.
Ratschenfreak
@nocomprende Das richtige Format ist offensichtlich , wenn die obere Grenze ein Parameter ist: for(markup = 5; markup <= MAX_MARKUP; ++markup). Alles andere würde es überkomplizieren.
Navin
1

Ich würde sagen, es geht nicht darum, ob Sie> oder> = verwenden sollten. Es geht darum, alles zu verwenden, was Sie zum Schreiben von aussagekräftigem Code benötigen.

Wenn Sie feststellen, dass Sie einen addieren / subtrahieren müssen, sollten Sie den anderen Operator verwenden. Ich finde, dass gute Dinge passieren, wenn Sie mit einem guten Modell Ihrer Domain beginnen. Dann schreibt sich die Logik von selbst.

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

Das ist viel ausdrucksvoller als

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

In anderen Fällen ist der andere Weg vorzuziehen:

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

Viel besser als

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

Bitte entschuldigen Sie die "primitive Besessenheit". Natürlich möchten Sie hier einen Velocity- und einen Money-Typ verwenden, aber der Kürze halber habe ich sie weggelassen. Der Punkt ist: Verwenden Sie eine präzisere Version, mit der Sie sich auf das Geschäftsproblem konzentrieren können, das Sie lösen möchten.

Sara
quelle
2
Beispiel Geschwindigkeitsbegrenzung ist ein schlechtes Beispiel. Eine Geschwindigkeit von 90 km / h in einer Zone von 90 km / h gilt nicht als Geschwindigkeitsüberschreitung. Eine Geschwindigkeitsbegrenzung ist inklusive.
Eidsonator
Sie sind absolut richtig, Hirnfurz meinerseits. Fühlen Sie sich frei, es zu bearbeiten, um ein besseres Beispiel zu verwenden, hoffentlich ist der Punkt nicht vollständig verloren.
Sara
0

Wie Sie in Ihrer Frage ausgeführt haben, ist das Testen auf Gleichheit mit Float-Variablen nicht zuverlässig.

Gleiches gilt für <=und>= .

Für ganzzahlige Typen gibt es jedoch keine derartigen Zuverlässigkeitsprobleme. Meiner Meinung nach drückte der Autor sie aus Meinung zum , welche lesbarer ist.

Ob Sie ihm zustimmen oder nicht, ist natürlich Ihre Meinung.

Dan Pichelman
quelle
Ist dies eine allgemein vertretene Ansicht von anderen Autoren und Grauhaaren auf dem Gebiet (ich habe eigentlich graues Haar)? Wenn es nur "One Reporter's Opinion" ist, kann ich den Schülern das sagen. Habe ich eigentlich. Noch nie von dieser Idee gehört.
Das Geschlecht des Autors ist irrelevant, aber ich habe meine Antwort trotzdem aktualisiert. Ich bevorzuge es, das für das jeweilige Problem, das ich löse, natürlichste auszuwählen <oder zu verwenden <=. Wie bereits erwähnt, ist eine FOR-Schleife <in C-Sprachen sinnvoller. Es gibt andere Anwendungsfälle, die dies begünstigen <=. Verwenden Sie alle Werkzeuge, die Ihnen zur Verfügung stehen, wann und wo dies angebracht ist.
Dan Pichelman
Nicht zustimmen. Es kann Zuverlässigkeitsprobleme mit Integer-Typen geben: in C, betrachten Sie: for (unsigned int i = n; i >= 0; i--)oder for (unsigned int i = x; i <= y; i++)wenn yzufällig UINT_MAX. Ups, diese Schleife für immer.
Jamesdlin
-1

Jede der Beziehungen <, <=, >=, >und auch == und !=haben ihre Anwendungsfälle für den Vergleich zweier Gleitkommazahlen. Jedes hat eine bestimmte Bedeutung und die entsprechende sollte ausgewählt werden.

Ich gebe Beispiele für Fälle, in denen Sie genau diesen Operator für jeden von ihnen möchten. (Beachten Sie jedoch die NaNs.)

  • Sie haben eine teure reine Funktion f, die einen Gleitkommawert als Eingabe verwendet. Um Ihre Berechnungen zu beschleunigen, fügen Sie einen Cache mit den zuletzt berechneten Werten hinzu, dh eine Zuordnung xzu einer Nachschlagetabelle f(x). Sie wollen wirklich verwenden ==, um die Argumente zu vergleichen.
  • Sie möchten wissen, ob Sie durch eine Zahl sinnvoll dividieren können x? Sie möchten wahrscheinlich verwenden x != 0.0.
  • Sie möchten wissen, ob ein Wert xim Einheitsintervall liegt? (x >= 0.0) && (x < 1.0)ist der richtige Zustand.
  • Sie haben die Determinante deiner Matrix berechnet und möchten feststellen, ob sie eindeutig ist? Es gibt keinen Grund, etwas anderes als zu verwenden d > 0.0.
  • Sie möchten wissen, ob Σ n = 1,…, ∞ n −α divergiert? Ich würde testen alpha <= 1.0.

Gleitkomma-Mathematik (im Allgemeinen) ist nicht genau. Das heißt aber nicht, dass Sie es fürchten, als Magie behandeln und zwei Fließkommazahlen nicht immer gleich behandeln sollten, wenn sie innerhalb liegen 1.0E-10. Wenn Sie dies tun, wird Ihre Mathematik wirklich durchbrochen und es werden alle seltsamen Dinge passieren.

  • Wenn Sie zum Beispiel den Fuzzy-Vergleich mit dem oben genannten Cache verwenden, treten urkomische Fehler auf. Schlimmer noch, die Funktion ist nicht mehr rein und der Fehler hängt von den zuvor berechneten Ergebnissen ab!
  • Ja, auch wenn x != 0.0und yein endlicher Gleitkommawert ist, y / xmuss er nicht endlich sein. Es kann jedoch von Bedeutung sein, zu wissen, ob y / xdie Operation aufgrund eines Überlaufs nicht endlich ist oder ob sie anfangs mathematisch nicht gut definiert war.
  • Wenn Sie eine Funktion haben, die voraussetzt, dass ein Eingabeparameter xim Einheitsintervall [0, 1) liegt, wäre ich wirklich verärgert, wenn er beim Aufruf mit x == 0.0oder einen Assertionsfehler auslösen würde x == 1.0 - 1.0E-14.
  • Die von Ihnen berechnete Determinante ist möglicherweise nicht genau. Wenn Sie jedoch so tun, als sei die Matrix nicht positiv bestimmt, wenn die berechnete Determinante ist 1.0E-30, wird nichts gewonnen. Alles, was Sie getan haben, war die Wahrscheinlichkeit zu erhöhen , die falsche Antwort zu geben.
  • Ja, Ihr Argument ist alphamöglicherweise von Rundungsfehlern betroffen und daher alpha <= 1.0möglicherweise richtig, obwohl der wahre mathematische Wert für den Ausdruck, aus dem er alphaberechnet wurde, möglicherweise größer als 1 war. Sie können derzeit jedoch möglicherweise nichts dagegen unternehmen.

Behandeln Sie Fehler wie immer in der Softwareentwicklung auf der entsprechenden Ebene und nur einmal. Wenn Sie Rundungsfehler in der Reihenfolge hinzufügen 1.0E-10(dies scheint der magische Wert zu sein, den die meisten Leute verwenden, ich weiß nicht warum), werden Sie bei jedem Vergleich von Gleitkommazahlen bald Fehler in der Reihenfolge von finden 1.0E+10

5gon12eder
quelle
1
Eine ausgezeichnete Antwort, allerdings nur für eine andere als die gestellte Frage.
Dewi Morgan
-2

Die Art der in einer Schleife verwendeten Bedingung kann die Art der Optimierungen einschränken, die ein Compiler zum Guten oder Schlechten ausführen kann. Zum Beispiel gegeben:

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

Ein Compiler könnte annehmen, dass die obige Bedingung dazu führen sollte, dass die Schleife nach der n-ten Durchgangsschleife beendet wird, es sei denn, n würde 65535 und die Schleife könnte auf eine andere Weise als um i, das n überschreitet, beendet werden. Wenn diese Bedingungen zutreffen, muss der Compiler Code generieren, der dazu führen würde, dass die Schleife ausgeführt wird, bis etwas anderes als die obige Bedingung dazu führt, dass sie beendet wird.

Wenn die Schleife stattdessen wie folgt geschrieben worden wäre:

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

Dann könnte ein Compiler sicher davon ausgehen, dass die Schleife niemals mehr als n-mal ausgeführt werden muss und somit effizienteren Code generieren kann.

Beachten Sie, dass ein Überlauf mit signierten Typen unangenehme Folgen haben kann. Gegeben:

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

Ein Compiler könnte dies folgendermaßen umschreiben:

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;

Eine solche Schleife würde sich identisch zum Original verhalten, wenn in den Berechnungen kein Überlauf auftritt. Sie könnte jedoch auch auf Hardwareplattformen für immer ausgeführt werden, auf denen ein Ganzzahlüberlauf normalerweise eine konsistente Umbruchssemantik aufweist.

Superkatze
quelle
Ja, ich denke, das ist etwas weiter im Lehrbuch. Noch keine Überlegung. Compiler-Optimierungen sind nützlich, um sie zu verstehen, und Überläufe müssen vermieden werden. Dies war jedoch nicht die eigentliche Motivation für meine Frage, die sich eher auf das Verständnis des Codes bezog. Ist es einfacher, x> 5 oder x> = 6 zu lesen? Nun, es kommt darauf an ...
Sofern Sie nicht für einen Arduino schreiben, wird der Maximalwert für int etwas höher als 65535 sein.
Corey
1
@Corey: Der Maximalwert für uint16_t wird auf jeder Plattform, auf der der Typ vorhanden ist, 65535 sein. Ich habe im ersten Beispiel uint16_t verwendet, weil ich erwarten würde, dass mehr Leute den genauen Maximalwert eines uint16_t kennen als den eines uint32_t. In beiden Fällen würde derselbe Punkt gelten. Das zweite Beispiel ist hinsichtlich des Typs "int" agnostisch und stellt einen kritischen Verhaltenspunkt dar, den viele Menschen bei modernen Compilern nicht erkennen.
Supercat