2013-07-15 11 views
16

Zwei Floating-Point-Nummern auf Gleichheit richtig zu testen ist etwas, das viele Leute, mich eingeschlossen, nicht vollständig verstehen. Heute habe ich jedoch darüber nachgedacht, wie einige Standardcontainer die Gleichheit in Bezug auf operator< definieren. Ich sehe immer Menschen mit Problemen, die die Gleichheit betreffen, aber niemals mit den anderen Vergleichsbeziehungen. Es gibt sogar silent versions von ihnen zu verwenden, die alles außer Gleichheit und Ungleichheit enthalten.Wenn der Operator <für Fließkommatypen ordnungsgemäß funktioniert, warum können wir ihn nicht für Gleichheitstests verwenden?

Unter der Annahme operator< Werke „richtig“, im Gegensatz zu operator==, warum können nicht, dass wir dies tun:

bool floateq(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

In der Tat, ich einen Test mit einer zusätzlichen Überlastung für Doppel lief, wie here gesehen, und es scheint, als die gleichen Fallen haben sie mit operator== Vergleich:

std::cout << "float->double vs double: " 
      << floateq(static_cast<double>(0.7f), 0.7) << " " 
      << (static_cast<double>(0.7f) == 0.7) << "\n"; 

Ausgang:

Schwimmer-> double vs double: 0 0

Bin ich alle Vergleichsoperatoren zu befürchten, oder gibt es eine andere Aspekt Gleitkommazahlen zu vergleichen, das ich nicht richtig zu verstehen?

+3

Es gibt nichts falsch mit Gleichheit-vergleichen Gleitkommawerte per se. Das Problem ist, dass sich das Ergebnis möglicherweise nicht wie erwartet verhält. –

Antwort

14

Die ==, <, >, <=, >= und != Betreiber gut funktionieren mit Gleitkommazahlen.

Sie scheinen die Prämisse, dass einige angemessene Umsetzung <sollten müssen vergleichen (double) 0.7f gleich 0,7. Das ist nicht der Fall.Wenn Sie 0.7f in double umwandeln, erhalten Sie 0x1.666666p-1. Jedoch ist 0.7 gleich 0x1.6666666666666p-1. Diese sind nicht numerisch gleich; in der Tat, (double)0.7f ist wesentlich kleiner als 0.7 --- es wäre lächerlich für sie gleich zu vergleichen.

Wenn Sie mit Gleitkommazahlen arbeiten, ist es wichtig, sich daran zu erinnern, dass es sich um Fließkommazahlen handelt und nicht um reelle Zahlen oder rationale Zahlen oder ähnliches. Sie müssen ihre Eigenschaften berücksichtigen und nicht die Eigenschaften, die jeder von ihnen haben möchte. Tun Sie dies und Sie vermeiden automatisch die meisten der oft zitierten "Fallstricke" beim Arbeiten mit Fließkommazahlen.

+0

Ich denke, das ist genau das, was ich brauchte. Es hat nicht darauf geklickt, dass der "Float" beim Casting das Extra-Präzisionsteil von einem "Double" unterscheidet, das es ursprünglich hatte. Also muss man weniger sein als das andere und deshalb nicht gleich. Es ist auch so eine Schande, denn es hätte mein Leben so viel einfacher gemacht, wenn das geklappt hätte: p – chris

+0

Gute Frage und auch gute Antwort. Für jeden, der Software schreibt, die sich mit Geld beschäftigt, verwende ich den Vergleich 'if (abs (a-b) <0,001)'. Normalerweise gehen Finanzsysteme nur zu einem halben oder höchstens zu einem Zehntel. Auf jeden Fall ist es wichtig, sorgfältig zu klären. –

+0

Es ist sehr riskant, finanzielle Berechnungen mit Fließkommazahlen durchzuführen. Im Allgemeinen ist es entweder mit einer _integer_ Anzahl von Pennies oder mit einer _decimal_ Darstellung gemacht. In jedem Fall müssen Sie immer noch sehr vorsichtig sein. –

1

Der folgende Code (was ich so verändert es kompiliert: Insbesondere der Aufruf von floateq-floatcmp geändert wurde) ausdruckt float->double vs double: 1 0, nicht 0 0 (wie man erwarten würde, wenn diese beiden Werte als Schwimmer zu vergleichen).

#include <iostream> 

bool floatcmp(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

int main() 
{ 
    std::cout << "float->double vs double: " 
       << floatcmp(static_cast<double>(0.7f), 0.7) << " " 
       << (static_cast<double>(0.7f) == 0.7) << "\n"; 
} 

Doch was für die Standardbibliothek zählt ist, dass operator< eine strenge schwache Ordnung definiert, die in der Tat tut es für Arten Punkt schweben.

Das Problem mit der Gleichheit ist, dass zwei Werte gleich aussehen können, wenn sie auf 4 oder 6 Stellen gerundet werden, aber tatsächlich völlig verschieden sind und als ungleich vergleichen.

+0

Entschuldigung, das von mir gepostete Codebeispiel stammt hauptsächlich aus meinem Test, mit dem ich verlinkt war. Das hat eine Überladung für zwei Schwimmer und eine Überlast für zwei Doppelgänger. Ich habe die Frage überarbeitet, um das besser widerzuspiegeln und den Namen konsistent zu halten. – chris

+0

In Bezug auf die tatsächliche Antwort, sollte nicht das bisschen Fluff aber viele Nachkommastellen in für "Operator <" aufgenommen werden? – chris

+0

'operator <' ist keine streng schwache Ordnung für Fließkommatypen, obwohl '! (1 tmyklebu

-2

Im Allgemeinen sollten alle Vergleichsoperationen für Gleitkommazahlen innerhalb einer bestimmten Genauigkeitsgrenze durchgeführt werden. Andernfalls können Sie von einem akkumulierten Rundungsfehler gebissen werden, der nicht mit geringer Genauigkeit erkannt wird, aber von Vergleichsoperatoren berücksichtigt wird. Es ist oft nicht so wichtig für die Sortierung.

Ein anderes Codebeispiel, das zeigt, dass Ihr Vergleich nicht funktioniert (http://ideone.com/mI4S76).

#include <iostream> 

bool floatcmp(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

int main() { 
    using namespace std; 

    float a = 0.1; 
    float b = 0.1; 

    // Introducing rounding error: 
    b += 1; 
    // Just to be sure change is not inlined 
    cout << "B after increase = " << b << endl; 
    b -= 1; 
    cout << "B after decrease = " << b << endl; 

    cout << "A " << (floatcmp(a, b) ? "equals" : "is not equal to") << "B" << endl; 
} 

Ausgang:

B after increase = 1.1 
B after decrease = 0.1 
A is not equal toB 
+0

Wo wird der zufällige Vergangenheits-Precision-Teil eines Floats gespeichert? Ich sehe nur ein Vorzeichen, einen Exponenten und einen Signifikanten. – tmyklebu

+0

Nun, ich muss zustimmen, dass die Formulierung, die ich verwendete, irgendwie frustrierend ist. Aktualisiert. – biocomp

+2

Das Problem mit Ihrer aktualisierten Antwort ist, dass Sie diese Toleranz irgendwie auswählen müssen.Es kann nicht zu groß sein, oder Sie werden fälschlicherweise sagen, dass zwei verschiedene Dinge gleich sind, und es kann nicht zu klein sein, oder Sie werden fälschlicherweise sagen, dass zwei "gleiche" Dinge verschieden sind. Das Auswählen der Toleranz, wenn es überhaupt möglich ist, erfordert normalerweise eine sorgfältige Analyse der Eingabe und der Abrundung, die sich aus der Berechnung ergeben, die Sie bei dieser Eingabe machen. Zu diesem Zeitpunkt ist es wahrscheinlich für Sie offensichtlich, dass Sie innerhalb von a vergleichen müssen Toleranz. – tmyklebu

1

Float und Double sind beide im binären Äquivalent der wissenschaftlichen Notation, mit einer festen Anzahl von signifikanten Bits. Wenn das Ergebnis der unendlichen Genauigkeit einer Berechnung nicht genau darstellbar ist, ist das tatsächliche Ergebnis das nächste, das genau darstellbar ist.

Es gibt zwei große Fallstricke mit diesem.

  1. Viele einfache, kurze Dezimaldehnungen, wie zB 0,1, sind im float oder double nicht exakt darstellbar.
  2. Zwei Ergebnisse, die in der reellen Zahlenarithmetik gleich wären, können in Gleitkommaarithmetik unterschiedlich sein. Zum Beispiel Gleitpunktarithmetik ist nicht assoziativ - (a + b) + c ist nicht unbedingt die gleiche wie a + (b + c)

Sie benötigen eine Toleranz für Vergleiche zu holen, der größer ist als der erwartete Rundungsfehler ist, aber klein genug, dass es akzeptabel ist, in Ihrem Programm, um Zahlen zu behandeln, die innerhalb der Toleranz gleich sind.

Wenn es keine solche Toleranz gibt, bedeutet dies, dass Sie den falschen Fließkommatyp verwenden oder überhaupt keinen Fließkommawert verwenden sollten. 32-Bit IEEE 754 hat eine derart begrenzte Genauigkeit, dass es sehr schwierig sein kann, eine geeignete Toleranz zu finden. Normalerweise ist 64-Bit eine viel bessere Wahl.

2

Wenn Sie Gleitkommazahlen verwenden, haben die relationalen Operatoren Bedeutungen, aber ihre Bedeutungen stimmen nicht unbedingt mit den tatsächlichen Zahlen überein.

Wenn Gleitkommawerte verwendet werden, um tatsächliche Zahlen (ihren normalen Zweck) darstellen, neigen die Operatoren verhalten sich wie folgt:

  • x > y und x >= y sowohl bedeuten, dass die numerische Größe, die x soll repräsentieren ist wahrscheinlich größer als y, und im schlimmsten Fall wahrscheinlich nicht viel weniger als y.

  • x < y und x <= y beide implizieren, dass die numerische Größe, die x sollte wahrscheinlich weniger vertreten als als y, und ist im schlimmsten Fall wahrscheinlich nicht viel größer als y.

  • x == y bedeutet, dass die numerischen Größen, die x und y nicht zu unterscheiden sind voneinander

Hinweis dar, dass, wenn x von float Typ ist und y ist vom Typ double, werden die oben genannten Bedeutungen erreicht werden Wenn das double Argument in float umgewandelt wird. In Abwesenheit einer spezifischen Umwandlung werden jedoch C und C++ (und auch viele andere Sprachen) einen float Operanden in double konvertieren, bevor ein Vergleich durchgeführt wird. Eine solche Umwandlung wird die Wahrscheinlichkeit, dass die Operanden als "nicht unterscheidbar" gemeldet werden, stark verringern, wird aber die Wahrscheinlichkeit, dass der Vergleich ein Ergebnis ergibt, das zu dem, was die beabsichtigten Zahlen tatsächlich anzeigen, stark erhöht. Betrachten wir zum Beispiel

float f = 16777217; 
double d = 16777216.5; 

Wenn beide Operanden zu float gegossen werden, wird der Vergleich zeigen, dass die Werte nicht zu unterscheiden sind. Wenn sie in double umgewandelt werden, zeigt der Vergleich an, dass d größer ist, obwohl der Wert f etwas größer darstellen soll. Als extremes Beispiel:

float f = 1E20f; 
float f2 = f*f; 
double d = 1E150; 
double d2 = d*d; 

Float f2 enthält die beste float Darstellung von 1E40. Double d2 enthält die besten double darstellung von 1E400. Die numerische Menge, dargestellt durch d2 is hundreds of orders of magnitude greater than that represented by f2 , but (doppelt) f2> d2 . By contrast, converting both operands to float would yield f2 == (Gleitkomma) d2 ', korrekte Meldung, dass die Werte nicht unterscheidbar sind.

PS - Ich bin mir durchaus bewusst, dass IEEE-Standards erfordern, dass Berechnungen Fließkommawerte stellen genaue Potenz von zwei Fraktionen, als ob durchgeführt werden, aber nur wenige Menschen den Code float f2 = f1/10.0; als „Set f2 auf die darstellbare Leistung sehen -von-zwei-Bruch, der am nächsten ist, ein Zehntel von dem in f1 zu sein ". Der Zweck des Codes besteht darin, f2 zu einem Zehntel von f1 zu machen. Aufgrund von Ungenauigkeit kann der Code diesen Zweck nicht perfekt erfüllen, aber in den meisten Fällen ist es hilfreicher, Gleitkommazahlen als tatsächliche numerische Größen zu betrachten, als sie als Zweier-Potenz zu betrachten.