it-swarm.com.de

Irreführt der Vergleich der Gleichheit der Float-Zahlen Nachwuchsentwickler, auch wenn in meinem Fall kein Rundungsfehler auftritt?

Zum Beispiel möchte ich eine Liste von Schaltflächen von 0,0.5, ... 5 anzeigen, die für jeweils 0,5 springt. Ich benutze dazu eine for-Schleife und habe eine andere Farbe an der Schaltfläche STANDARD_LINE:

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0;i<=MAX;i=i+DIFF){
    button.text=i+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

In diesem Fall sollte es keine Rundungsfehler geben, da jeder Wert in IEEE 754 genau ist. Ich habe jedoch Probleme, ihn zu ändern, um einen Gleitkomma-Gleichheitsvergleich zu vermeiden:

var MAX=10;
var STANDARD_LINE=3;

for(var i=0;i<=MAX;i++){
    button.text=i/2.0+'';
    if(i==STANDARD_LINE/2.0){
      button.color='red';
    }
}

Einerseits ist der ursprüngliche Code einfacher und für mich weiterleitend. Aber es gibt eine Sache, über die ich nachdenke: Ist i == STANDARD_LINE irreführend für Junior-Teamkollegen? Versteckt es die Tatsache, dass Gleitkommazahlen Rundungsfehler aufweisen können? Nach dem Lesen der Kommentare aus diesem Beitrag:

https://stackoverflow.com/questions/33646148/is-hardcode-float-precise-if-it-can-be-represented-by-binary-format-in-ieee-754

es scheint, dass es viele Entwickler gibt, die nicht wissen, dass einige Float-Zahlen genau sind. Sollte ich Vergleiche der Gleichheit von Gleitkommazahlen vermeiden, auch wenn dies in meinem Fall gültig ist? Oder denke ich darüber nach?

31
ocomfd

Ich würde aufeinanderfolgende Gleitkommaoperationen immer vermeiden, es sei denn, das Modell, das ich berechne, erfordert sie. Gleitkomma-Arithmetik ist für die meisten nicht intuitiv und eine Hauptfehlerquelle. Und die Fälle, in denen es Fehler verursacht, von denen zu erzählen, in denen dies nicht der Fall ist, ist eine noch subtilere Unterscheidung!

Daher ist die Verwendung von Floats als Schleifenzähler ein Fehler, der darauf wartet, aufgetreten zu werden, und der zumindest einen fetten Hintergrundkommentar erfordert, der erklärt, warum es in Ordnung ist, hier 0,5 zu verwenden, und dass dies vom spezifischen numerischen Wert abhängt. Zu diesem Zeitpunkt ist das Umschreiben des Codes zur Vermeidung von Float-Zählern wahrscheinlich die besser lesbare Option. Und Lesbarkeit ist neben Korrektheit in der Hierarchie der beruflichen Anforderungen.

116
Kilian Foth

In der Regel sollten Schleifen so geschrieben werden, dass sie daran denken, etwas zu tun n mal. Wenn Sie Gleitkommaindizes verwenden, geht es nicht mehr darum, etwas n Mal zu tun, sondern zu laufen, bis eine Bedingung erfüllt ist. Wenn dieser Zustand dem von so vielen Programmierern erwarteten i<n Sehr ähnlich ist, scheint der Code eine Sache zu tun, wenn er tatsächlich eine andere tut, was von Programmierern, die den Code überfliegen, leicht falsch interpretiert werden kann.

Es ist etwas subjektiv, aber meiner bescheidenen Meinung nach sollten Sie dies tun, wenn Sie eine Schleife neu schreiben können, um einen ganzzahligen Index für eine feste Anzahl von Schleifen zu verwenden. Betrachten Sie also die folgende Alternative:

var DIFF=0.5;                           // pixel increment
var MAX=Math.floor(5.0/DIFF);           // 5.0 is max pixel width
var STANDARD_LINE=Math.floor(1.5/DIFF); // 1.5 is pixel width

for(var i=0;i<=MAX;i++){
    button.text=(i*DIFF)+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

Die Schleife arbeitet in ganzen Zahlen. In diesem Fall ist i eine Ganzzahl und STANDARD_LINE Wird ebenfalls zu einer Ganzzahl gezwungen. Dies würde natürlich die Position Ihrer Standardzeile ändern, wenn es eine Rundung geben würde, und ebenso für MAX, daher sollten Sie sich weiterhin bemühen, eine Rundung für ein genaues Rendern zu verhindern. Sie haben jedoch immer noch den Vorteil, dass Sie die Parameter in Pixel und nicht in ganzen Zahlen ändern können, ohne sich um den Vergleich von Gleitkommazahlen kümmern zu müssen.

40
Neil

Ich stimme allen anderen Antworten zu, dass die Verwendung einer nicht ganzzahligen Schleifenvariablen im Allgemeinen ein schlechter Stil ist, selbst in Fällen wie diesem, in denen sie korrekt funktioniert. Aber es scheint mir, dass es einen anderen Grund gibt, warum es hier ein schlechter Stil ist.

Ihr Code "weiß", dass die verfügbaren Linienbreiten genau das Vielfache von 0,5 von 0 bis 5,0 sind. Sollte es? Es scheint, dass dies eine Entscheidung für die Benutzeroberfläche ist, die sich leicht ändern kann (z. B. möchten Sie, dass die Lücken zwischen den verfügbaren Breiten größer werden als die Breiten. 0,25, 0,5, 0,75, 1,0, 1,5, 2,0, 2,5, 3,0, 4,0, 5.0 oder so).

Ihr Code "weiß", dass die verfügbaren Linienbreiten alle "Schöne" Darstellungen sowohl als Gleitkommazahlen als auch als Dezimalstellen haben. Das scheint sich auch zu ändern. (Vielleicht möchten Sie irgendwann 0,1, 0,2, 0,3, ...)

Ihr Code "weiß", dass der Text, der auf die Schaltflächen gesetzt werden soll, einfach das ist, was Javascript aus diesen Gleitkommawerten macht. Das scheint sich auch zu ändern. (ZB möchten Sie vielleicht eines Tages Breiten wie 1/3, die Sie wahrscheinlich nicht als 0,33333333333333 oder was auch immer anzeigen möchten. Oder Sie möchten "1,0" anstelle von "1" sehen, um die Konsistenz mit "1,5" zu gewährleisten. .)

All dies fühlt sich für mich wie Manifestationen einer einzigen Schwäche an, die eine Art Vermischung von Schichten ist. Diese Gleitkommazahlen sind Teil der internen Logik der Software. Der auf den Schaltflächen angezeigte Text ist Teil der Benutzeroberfläche. Sie sollten getrennter sein als im Code hier. Begriffe wie "Welche davon ist die Standardeinstellung, die hervorgehoben werden sollte?" sind Fragen der Benutzeroberfläche und sollten wahrscheinlich nicht an diese Gleitkommawerte gebunden werden. Und Ihre Schleife hier ist wirklich (oder sollte es zumindest sein) eine Schleife über Schaltflächen, nicht über Linienbreiten. Auf diese Weise geschrieben, verschwindet die Versuchung, eine Schleifenvariable zu verwenden, die nicht ganzzahlige Werte annimmt: Sie würden nur aufeinanderfolgende ganze Zahlen oder eine for ... in/for ... -Schleife verwenden.

Mein Gefühl ist, dass die meisten Fälle, in denen man versucht sein könnte, nicht ganzzahlige Zahlen zu durchlaufen, so sind: Es gibt andere Gründe, die völlig unabhängig von numerischen Problemen sind, warum der Code anders organisiert werden sollte. (Nicht alle Fälle; Ich kann mir vorstellen, dass einige mathematische Algorithmen am besten in Form einer Schleife über nicht ganzzahlige Werte ausgedrückt werden können.)

20

Ein Code-Geruch besteht darin, Floats in einer solchen Schleife zu verwenden.

Looping kann auf viele Arten durchgeführt werden, aber in 99,9% der Fälle sollten Sie sich an ein Inkrement von 1 halten, da sonst definitiv Verwirrung herrscht, nicht nur bei Nachwuchsentwicklern.

8
Pieter B

Ja, das möchten Sie vermeiden.

Gleitkommazahlen sind eine der größten Fallen für den ahnungslosen Programmierer (was meiner Erfahrung nach fast jeder bedeutet). Von der Abhängigkeit von Gleitkomma-Gleichheitstests bis hin zur Darstellung von Geld als Gleitkomma ist alles ein großer Sumpf. Das Aufaddieren eines Schwimmers auf den anderen ist einer der größten Straftäter. Es gibt ganze Bände wissenschaftlicher Literatur über solche Dinge.

Verwenden Sie Gleitkommazahlen genau an den Stellen, an denen sie geeignet sind, z. B. wenn Sie tatsächliche mathematische Berechnungen dort durchführen, wo Sie sie benötigen (z. B. Trigonometrie, Zeichnen von Funktionsgraphen usw.), und seien Sie bei seriellen Operationen äußerst vorsichtig. Gleichheit ist richtig. Das Wissen darüber, welcher bestimmte Satz von Zahlen nach IEEE-Standards genau ist, ist sehr geheimnisvoll und ich würde mich niemals darauf verlassen.

In Ihrem Fall wird dort nach Murphys Gesetz der Punkt kommen, an dem das Management möchte, dass Sie nicht 0.0, 0.5, 1.0 ... sondern 0.0, 0,4, 0,8 ... oder was auch immer; Sie werden sofort enttäuscht sein und Ihr Junior-Programmierer (oder Sie selbst) wird lange und hart debuggen, bis Sie das Problem finden.

In Ihrem speziellen Code hätte ich tatsächlich eine Ganzzahlschleifenvariable. Es repräsentiert die Schaltfläche ith, nicht die laufende Nummer.

Und ich würde wahrscheinlich aus Gründen der Klarheit nicht schreiben i/2 aber i*0.5 was deutlich macht, was los ist.

var BUTTONS=11;
var STANDARD_LINE=3;

for(var i=0; i<BUTTONS; i++) {
    button.text = (i*0.5)+'';
    if (i==STANDARD_LINE) {
      button.color='red';
    }
}

Hinweis: Wie in den Kommentaren erwähnt, hat JavaScript keinen separaten Typ für Ganzzahlen. Ganzzahlen mit bis zu 15 Ziffern sind jedoch garantiert genau/sicher (siehe https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), daher für Argumente wie dieses ("ist es verwirrender/fehleranfälliger, mit ganzen oder nicht ganzen Zahlen zu arbeiten") sind angemessen nahe daran, einen separaten Typ "im Geiste" zu haben; Im täglichen Gebrauch (Schleifen, Bildschirmkoordinaten, Array-Indizes usw.) gibt es keine Überraschungen, wenn Ganzzahlen als Number als JavaScript dargestellt werden.

3
AnoE

Ich denke nicht, dass einer Ihrer Vorschläge gut ist. Stattdessen würde ich eine Variable für die Anzahl der Schaltflächen basierend auf dem Maximalwert und dem Abstand einführen. Dann ist es einfach genug, die Indizes der Schaltfläche selbst zu durchlaufen.

function precisionRound(number, precision) {
  let factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

var maxButtonValue = 5.0;
var buttonSpacing = 0.5;

let countEstimate = precisionRound(maxButtonValue / buttonSpacing, 5);
var buttonCount = Math.floor(countEstimate) + 1;

var highlightPosition = 3;
var highlightColor = 'red';

for (let i=0; i < buttonCount; i++) {
    let buttonValue = i / buttonSpacing;
    button.text = buttonValue.toString();
    if (i == highlightPosition) {
        button.color = highlightColor;
    }
}

Es könnte mehr Code sein, aber es ist auch lesbarer und robuster.

1
Jared Goguen

Sie können das Ganze vermeiden, indem Sie den angezeigten Wert berechnen, anstatt den Schleifenzähler als Wert zu verwenden:

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0; (i*DIFF) < MAX ; i=i+1){
    var val = i * DIFF

    button.text=val+'';

    if(val==STANDARD_LINE){
      button.color='red';
    }
}
0
Arnab Datta