Warum ist 24.0000 in MATLAB nicht gleich 24.0000?

68

Ich schreibe ein Programm, in dem ich doppelte Punkte löschen muss, die in einer Matrix gespeichert sind. Das Problem ist, dass MATLAB bei der Überprüfung, ob sich diese Punkte in der Matrix befinden, diese in der Matrix nicht erkennen kann, obwohl sie vorhanden sind.

Im folgenden Code intersectionserhält die Funktion die Schnittpunkte:

[points(:,1), points(:,2)] = intersections(...
    obj.modifiedVGVertices(1,:), obj.modifiedVGVertices(2,:), ...
    [vertex1(1) vertex2(1)], [vertex1(2) vertex2(2)]);

Das Ergebnis:

>> points
points =
   12.0000   15.0000
   33.0000   24.0000
   33.0000   24.0000

>> vertex1
vertex1 =
    12
    15

>> vertex2    
vertex2 =
    33
    24

Zwei Punkte ( vertex1und vertex2) sollten aus dem Ergebnis entfernt werden. Dies sollte mit den folgenden Befehlen erfolgen:

points = points((points(:,1) ~= vertex1(1)) | (points(:,2) ~= vertex1(2)), :);
points = points((points(:,1) ~= vertex2(1)) | (points(:,2) ~= vertex2(2)), :);

Danach haben wir dieses unerwartete Ergebnis:

>> points
points =
   33.0000   24.0000

Das Ergebnis sollte eine leere Matrix sein. Wie Sie sehen können, ist das erste (oder zweite?) Paar von[33.0000 24.0000] eliminiert, aber nicht das zweite.

Dann habe ich diese beiden Ausdrücke überprüft:

>> points(1) ~= vertex2(1)
ans =
     0
>> points(2) ~= vertex2(2)
ans =
     1   % <-- It means 24.0000 is not equal to 24.0000?

Worin besteht das Problem?


Überraschenderweise habe ich ein neues Skript erstellt, das nur die folgenden Befehle enthält:

points = [12.0000   15.0000
          33.0000   24.0000
          33.0000   24.0000];

vertex1 = [12 ;  15];
vertex2 = [33 ;  24];

points = points((points(:,1) ~= vertex1(1)) | (points(:,2) ~= vertex1(2)), :);
points = points((points(:,1) ~= vertex2(1)) | (points(:,2) ~= vertex2(2)), :);

Das Ergebnis wie erwartet:

>> points
points =  
   Empty matrix: 0-by-2
Kamran Bigdely
quelle
1
Dies wird auch angesprochen wurde hier
ChrisF
2
@Kamran: Entschuldigung, ich habe nicht auf die Gefahren des Gleitkomma-Vergleichs hingewiesen, als Sie in Ihrer anderen Frage nach dem Vergleichen von Werten gefragt haben. Mir ist nicht sofort aufgefallen, dass Sie auf dieses Problem stoßen könnten.
Gnovice
2
Als Randnotiz vergleichen 1.2 - 0.2 - 1 == 0und 1.2 - 1 - 0.2 == 0. Überraschend, nicht wahr? Wenn Sie mit Gleitkommazahlen arbeiten, ist die Reihenfolge der Operationen von Bedeutung.
Jub0bs
1
@Tick Tock: Als Autor der Frage konnte ich den Titel, den Sie für meine Frage gewählt haben, nicht einmal verstehen. Es spiegelte auch nicht die Tatsache wider, dass MATLAB beim Ausdrucken der Variablen nicht den gesamten Gleitkommateil der Zahl anzeigt.
Kamran Bigdely
1
@ m7913d, ich verstehe. Normalerweise setzen sie jedoch das Etikett "Duplikat" auf die neuere Frage. Bitte lesen Sie die Regeln für doppelte Etiketten: meta.stackexchange.com/questions/10841/…
Kamran Bigdely

Antworten:

98

Das Problem, das Sie haben, hängt damit zusammen, wie Gleitkommazahlen auf einem Computer dargestellt werden. Eine detailliertere Diskussion der Gleitkomma-Darstellungen erscheint gegen Ende meiner Antwort (Abschnitt "Gleitkomma-Darstellung"). Die TL; DR- Version: Da Computer über einen begrenzten Speicher verfügen, können Zahlen nur mit endlicher Genauigkeit dargestellt werden. Daher ist die Genauigkeit von Gleitkommazahlen auf eine bestimmte Anzahl von Dezimalstellen beschränkt (etwa 16 signifikante Stellen für Werte mit doppelter Genauigkeit , die in MATLAB verwendete Standardeinstellung).

Tatsächliche vs. angezeigte Präzision

Nun das spezielle Beispiel in der Frage zu befassen ... während 24.0000und 24.0000werden angezeigt in der gleichen Weise, stellt sich heraus , dass sie tatsächlich durch sehr kleine Dezimal - Beträge in diesem Fall unterscheiden. Sie sehen es nicht, da MATLAB standardmäßig nur 4 signifikante Stellen anzeigt , wodurch die Gesamtanzeige sauber und ordentlich bleibt. Wenn Sie die volle Genauigkeit sehen möchten, sollten Sie entweder den format longBefehl ausgeben oder eine hexadezimale Darstellung der Zahl anzeigen :

>> pi
ans =
    3.1416
>> format long
>> pi
ans =
   3.141592653589793
>> num2hex(pi)
ans =
400921fb54442d18

Initialisierte Werte vs. berechnete Werte

Da es nur eine endliche Anzahl von Werten gibt, die für eine Gleitkommazahl dargestellt werden können, kann eine Berechnung zu einem Wert führen, der zwischen zwei dieser Darstellungen liegt. In einem solchen Fall muss das Ergebnis auf einen von ihnen gerundet werden. Dies führt zu einem kleinen Fehler bei der Maschinengenauigkeit . Dies bedeutet auch, dass das Initialisieren eines Werts direkt oder durch eine Berechnung leicht unterschiedliche Ergebnisse liefern kann. Beispielsweise hat der Wert 0.1keine exakte Gleitkommadarstellung (dh er wird leicht abgerundet), sodass Sie aufgrund der Art und Weise, wie sich Rundungsfehler ansammeln, kontraintuitive Ergebnisse wie diese erhalten:

>> a=sum([0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]);  % Sum 10 0.1s
>> b=1;                                               % Initialize to 1
>> a == b
ans =
  logical
   0                % They are unequal!
>> num2hex(a)       % Let's check their hex representation to confirm
ans =
3fefffffffffffff
>> num2hex(b)
ans =
3ff0000000000000

Wie man Gleitkomma-Vergleiche richtig handhabt

Da sich Gleitkommawerte um sehr kleine Beträge unterscheiden können, sollten Vergleiche durchgeführt werden, indem überprüft wird, ob die Werte innerhalb eines Bereichs (dh einer Toleranz) voneinander liegen und nicht genau gleich sind. Zum Beispiel:

a = 24;
b = 24.000001;
tolerance = 0.001;
if abs(a-b) < tolerance, disp('Equal!'); end

zeigt "Gleich!" an.

Sie können dann Ihren Code in Folgendes ändern:

points = points((abs(points(:,1)-vertex1(1)) > tolerance) | ...
                (abs(points(:,2)-vertex1(2)) > tolerance),:)

Gleitkomma-Darstellung

Ein guter Überblick über Gleitkommazahlen (und insbesondere den IEEE 754-Standard für Gleitkomma-Arithmetik ) sollte jeder Informatiker über Gleitkomma-Arithmetik von David Goldberg wissen .

Eine binäre Gleitkommazahl wird tatsächlich durch drei ganze Zahlen dargestellt: ein Vorzeichenbit s, einen Signifikanten (oder Koeffizienten / Bruch) bund einen Exponenten e. Beim Gleitkommaformat mit doppelter Genauigkeit wird jede Zahl durch 64 Bit dargestellt, die wie folgt im Speicher angeordnet sind:

Geben Sie hier die Bildbeschreibung ein

Der reale Wert kann dann mit der folgenden Formel ermittelt werden:

Geben Sie hier die Bildbeschreibung ein

Dieses Format ermöglicht Zahlendarstellungen im Bereich von 10 ^ -308 bis 10 ^ 308. Für MATLAB erhalten Sie diese Grenzwerte von realminund realmax:

>> realmin
ans =
    2.225073858507201e-308
>> realmax
ans =
    1.797693134862316e+308

Da es eine endliche Anzahl von Bits gibt, die zur Darstellung einer Gleitkommazahl verwendet werden, gibt es nur so viele endliche Zahlen, die innerhalb des oben angegebenen Bereichs dargestellt werden können. Berechnungen führen häufig zu einem Wert, der nicht genau mit einer dieser endlichen Darstellungen übereinstimmt. Daher müssen die Werte gerundet werden. Diese Maschinengenauigkeitsfehler machen sich auf unterschiedliche Weise bemerkbar, wie in den obigen Beispielen erläutert.

Um diese Rundungsfehler besser zu verstehen, ist es nützlich, die relative Gleitkomma-Genauigkeit der Funktion zu betrachten eps, die den Abstand von einer bestimmten Zahl zur nächstgrößeren Gleitkomma-Darstellung quantifiziert:

>> eps(1)
ans =
     2.220446049250313e-16
>> eps(1000)
ans =
     1.136868377216160e-13

Beachten Sie, dass die Genauigkeit relativ zur Größe einer bestimmten dargestellten Zahl ist. Größere Zahlen haben größere Abstände zwischen Gleitkommadarstellungen und daher weniger Genauigkeitsstellen nach dem Dezimalpunkt. Dies kann bei einigen Berechnungen eine wichtige Überlegung sein. Betrachten Sie das folgende Beispiel:

>> format long              % Display full precision
>> x = rand(1, 10);         % Get 10 random values between 0 and 1
>> a = mean(x)              % Take the mean
a =
   0.587307428244141
>> b = mean(x+10000)-10000  % Take the mean at a different scale, then shift back
b =
   0.587307428244458

Beachten Sie, dass wir einen Wert erhalten, der sich für die letzten 3 signifikanten Stellen unterscheidet , wenn wir die Werte von xvom Bereich [0 1]in den Bereich verschieben [10000 10001], einen Mittelwert berechnen und dann den Mittelwertversatz zum Vergleich subtrahieren. Dies zeigt, wie ein Versatz oder eine Skalierung von Daten die Genauigkeit der daran durchgeführten Berechnungen ändern kann, was bei bestimmten Problemen berücksichtigt werden muss.

gnovice
quelle
Warum kann ich diesen kleinen Dezimalbetrag nicht sehen?
Kamran Bigdely
2
Sie können es sehen, wenn Sie die Variable in der Matrixansicht anzeigen. Rechtsklick auf Variable -> "Auswahl anzeigen" oder so? Ich habe hier kein MATLAB, daher kann ich es nicht überprüfen.
Atsjoo
4
Sie können auch kleine Unterschiede feststellen, indem Sie an der Eingabeaufforderung "format long" eingeben.
Gnovice
2
Sie haben Recht: Formatieren Sie lange Punkte = 12.000000000000000 15.000000000000000 33.000000000000000 23.9999999999996 33.000000000000000 24.000000000000000
Kamran Bigdely
6
"format hex" kann hier manchmal sogar mehr als lange formatieren.
Sam Roberts
23

Schauen Sie sich diesen Artikel an: Die Gefahren des Gleitkommas . Obwohl seine Beispiele in FORTRAN sind, hat es Sinn für praktisch jede moderne Programmiersprache, einschließlich MATLAB. Ihr Problem (und die Lösung dafür) wird im Abschnitt "Sichere Vergleiche" beschrieben.

Rorick
quelle
1
Ich habe es vor einiger Zeit entdeckt und war sehr beeindruckt = = Jetzt empfehle ich es immer in ähnlichen Situationen.
Rorick
Archivierte Version dieser hervorragenden Ressource!
Wizclown
13

Art

format long g

Dieser Befehl zeigt den vollständigen Wert der Zahl an. Es ist wahrscheinlich so etwas wie 24.00000021321! = 24.00000123124

KitsuneYMG
quelle
7

Versuche zu schreiben

0,1 + 0,1 + 0,1 == 0,3.

Warnung: Sie könnten über das Ergebnis überrascht sein!

Andrey Rubshtein
quelle
Ich habe es versucht und es gibt 0 zurück. Aber ich sehe nicht, was es mit dem obigen Problem zu tun hat. Können Sie es mir bitte erklären?
Max
6
Dies liegt daran, dass 0.1 mit einem Gleitkommafehler einhergeht und wenn Sie drei solcher Begriffe addieren, addieren sich die Fehler nicht unbedingt zu 0. Das gleiche Problem führt dazu, dass (Gleitkomma) 24 nicht genau gleich (einem anderen Gleitkomma) 24 ist .
Derek
2

Vielleicht sind die beiden Zahlen wirklich 24.0 und 24.000000001, aber Sie sehen nicht alle Dezimalstellen.

Jimmy J.
quelle
1

Überprüfen Sie die Matlab EPS-Funktion .

Matlab verwendet Gleitkomma-Mathematik mit einer Genauigkeit von bis zu 16 Stellen (nur 5 werden angezeigt).

jle
quelle