Vergleichen Sie double mit null mit epsilon

214

Heute habe ich einen C ++ - Code (von jemand anderem geschrieben) durchgesehen und diesen Abschnitt gefunden:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}

Ich versuche herauszufinden, ob das überhaupt Sinn macht.

Die Dokumentation für epsilon()sagt:

Die Funktion gibt die Differenz zwischen 1 und dem kleinsten Wert größer als 1 zurück, der darstellbar ist [durch ein Doppel].

Gilt dies auch für 0, dh epsilon()ist der kleinste Wert größer als 0? Oder gibt es Zahlen zwischen 0und 0 + epsilondie durch a dargestellt werden können double?

Wenn nicht, ist der Vergleich dann nicht gleichbedeutend mit someValue == 0.0?

Sebastian Krysmanski
quelle
3
Das Epsilon um 1 wird höchstwahrscheinlich viel höher sein als das um 0, daher wird es wahrscheinlich Werte zwischen 0 und 0 + epsilon_at_1 geben. Ich denke, der Autor dieses Abschnitts wollte etwas Kleines verwenden, aber er wollte keine magische Konstante verwenden, also verwendete er nur diesen im Wesentlichen willkürlichen Wert.
Enobayram
2
Der Vergleich von Gleitkommazahlen ist schwierig, und die Verwendung von Epsilon oder Schwellenwerten wird sogar empfohlen. Bitte beziehen Sie sich auf: cs.princeton.edu/introcs/91float und cygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey
40
Erster Link ist 403.99999999
graham.reeds
6
IMO, in diesem Fall ist die Verwendung von numeric_limits<>::epsilonirreführend und irrelevant. Wir wollen 0 annehmen, wenn der tatsächliche Wert nicht mehr als einige ε von 0 abweicht. Und ε sollte basierend auf der Problemspezifikation und nicht basierend auf einem maschinenabhängigen Wert ausgewählt werden. Ich würde vermuten, dass das aktuelle Epsilon nutzlos ist, da bereits wenige FP-Operationen einen größeren Fehler verursachen können.
Andrey Vihrov
1
+1. epsilon ist nicht das kleinstmögliche, kann aber bei den meisten praktischen technischen Aufgaben den vorgegebenen Zweck erfüllen, wenn Sie wissen, welche Präzision Sie benötigen und was Sie tun.
SChepurin

Antworten:

192

Unter der Annahme eines 64-Bit-IEEE-Doppels gibt es eine 52-Bit-Mantisse und einen 11-Bit-Exponenten. Lassen Sie es uns in Stücke brechen:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1

Die kleinste darstellbare Zahl größer als 1:

1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52

Deshalb:

epsilon = (1 + 2^-52) - 1 = 2^-52

Gibt es Zahlen zwischen 0 und epsilon? Viel ... ZB ist die minimale positiv darstellbare (normale) Zahl:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022

Tatsächlich gibt es (1022 - 52 + 1)×2^52 = 4372995238176751616Zahlen zwischen 0 und epsilon, was 47% aller positiv darstellbaren Zahlen entspricht ...

Yakov Galka
quelle
27
So seltsam, dass man "47% der positiven Zahlen" sagen kann :)
Konfigurator
13
@configurator: Nein, das kann man nicht sagen (es gibt kein 'natürliches' endliches Maß). Man kann aber sagen "47% der positiv darstellbaren Zahlen".
Yakov Galka
1
@ybungalobill Ich kann es nicht herausfinden. Der Exponent hat 11 Bits: 1 Vorzeichenbit und 10 Wertbits. Warum ist 2 ^ -1022 und nicht 2 ^ -1024 die kleinste positive Zahl?
Pavlo Dyban
3
@PavloDyban: einfach weil Exponenten kein Vorzeichenbit haben. Sie werden als Offsets codiert: Wenn der codierte Exponent ist, 0 <= e < 2048wird die Mantisse mit 2 multipliziert mit der Potenz von e - 1023. ZB wird der Exponent von 2^0als e=1023, 2^1als e=1024und 2^-1022als codiert e=1. Der Wert von e=0ist für Subnormen und die reale Null reserviert.
Yakov Galka
2
@PavloDyban: 2^-1022ist auch die kleinste normale Zahl. Die kleinste Zahl ist tatsächlich 0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074. Dies ist subnormal, was bedeutet, dass der Mantissenteil kleiner als 1 ist und daher mit dem Exponenten codiert wird e=0.
Yakov Galka
17

Der Test ist sicherlich nicht der gleiche wie someValue == 0. Die ganze Idee von Gleitkommazahlen ist, dass sie einen Exponenten und einen Signifikanten speichern. Sie stellen daher einen Wert mit einer bestimmten Anzahl von binären signifikanten Genauigkeitszahlen dar (53 im Fall eines IEEE-Doppels). Die darstellbaren Werte sind nahe 0 viel dichter gepackt als nahe 1.

Um ein vertrauteres Dezimalsystem zu verwenden, nehmen Sie an, Sie speichern einen Dezimalwert "bis 4 signifikante Zahlen" mit Exponent. Dann ist der nächste darstellbare Wert größer als 1ist 1.001 * 10^0und epsilonist 1.000 * 10^-3. Ist 1.000 * 10^-4aber auch darstellbar, vorausgesetzt der Exponent kann -4 speichern. Sie können mein Wort dafür nehmen, dass ein IEEE-Double Exponenten speichern kann , die kleiner sind als der Exponent von epsilon.

Anhand dieses Codes können Sie nicht allein erkennen, ob es sinnvoll ist, ihn epsilonspeziell als Bindung zu verwenden. Sie müssen sich den Kontext ansehen. Es kann sein, dass dies epsiloneine vernünftige Schätzung des Fehlers in der berechneten Berechnung ist someValue, und es kann sein, dass dies nicht der Fall ist.

Steve Jessop
quelle
2
Guter Punkt, aber selbst wenn dies der Fall ist, wäre es besser, den Fehler in einer vernünftig benannten Variablen zu binden und im Vergleich zu verwenden. So wie es aussieht, unterscheidet es sich nicht von einer magischen Konstante.
Enobayram
Vielleicht hätte ich in meiner Frage klarer sein sollen: Ich habe nicht gefragt, ob epsilon groß genug ist, um Rechenfehler abzudecken, sondern ob dieser Vergleich gleich ist someValue == 0.0oder nicht.
Sebastian Krysmanski
13

Es gibt Zahlen zwischen 0 und epsilon, da epsilon die Differenz zwischen 1 und der nächsthöheren Zahl ist, die über 1 dargestellt werden kann, und nicht die Differenz zwischen 0 und der nächsthöheren Zahl, die über 0 dargestellt werden kann (wenn dies der Fall wäre) Code würde sehr wenig tun): -

#include <limits>

int main ()
{
  struct Doubles
  {
      double one;
      double epsilon;
      double half_epsilon;
  } values;

  values.one = 1.0;
  values.epsilon = std::numeric_limits<double>::epsilon();
  values.half_epsilon = values.epsilon / 2.0;
}

Stoppen Sie das Programm mit einem Debugger am Ende von main und sehen Sie sich die Ergebnisse an. Sie werden feststellen, dass sich epsilon / 2 von epsilon, zero und one unterscheidet.

Diese Funktion nimmt also Werte zwischen +/- Epsilon an und macht sie zu Null.

Skizz
quelle
5

Eine Annäherung von Epsilon (kleinstmöglicher Unterschied) um eine Zahl (1,0, 0,0, ...) kann mit dem folgenden Programm gedruckt werden. Es wird die folgende Ausgabe gedruckt:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Ein wenig Nachdenken macht deutlich, dass das Epsilon umso kleiner wird, je kleiner die Zahl ist, die wir für die Betrachtung seines Epsilon-Werts verwenden, da sich der Exponent an die Größe dieser Zahl anpassen kann.

#include <stdio.h>
#include <assert.h>
double getEps (double m) {
  double approx=1.0;
  double lastApprox=0.0;
  while (m+approx!=m) {
    lastApprox=approx;
    approx/=2.0;
  }
  assert (lastApprox!=0);
  return lastApprox;
}
int main () {
  printf ("epsilon for 0.0 is %e\n", getEps (0.0));
  printf ("epsilon for 1.0 is %e\n", getEps (1.0));
  return 0;
}
pbhd
quelle
2
Welche Implementierungen haben Sie überprüft? Dies ist bei GCC 4.7 definitiv nicht der Fall.
Anton Golov
3

Angenommen, wir arbeiten mit Spielzeug-Gleitkommazahlen, die in ein 16-Bit-Register passen. Es gibt ein Vorzeichenbit, einen 5-Bit-Exponenten und eine 10-Bit-Mantisse.

Der Wert dieser Gleitkommazahl ist die Mantisse, die als binärer Dezimalwert interpretiert wird, mal zwei hoch der Potenz des Exponenten.

Um 1 ist der Exponent gleich Null. Die kleinste Ziffer der Mantisse ist also ein Teil von 1024.

In der Nähe von 1/2 ist der Exponent minus eins, sodass der kleinste Teil der Mantisse halb so groß ist. Mit einem Fünf-Bit-Exponenten kann er negative 16 erreichen. An diesem Punkt ist der kleinste Teil der Mantisse einen Teil in 32 m wert. Und bei einem negativen 16-Exponenten liegt der Wert um einen Teil in 32k, viel näher an Null als das Epsilon um einen, den wir oben berechnet haben!

Dies ist ein Spielzeug-Gleitkommamodell, das nicht alle Macken eines echten Gleitkommasystems widerspiegelt, aber die Fähigkeit, Werte zu reflektieren, die kleiner als epsilon sind, ist mit echten Gleitkommawerten ziemlich ähnlich.

Yakk - Adam Nevraumont
quelle
3

Die Differenz zwischen Xund dem nächsten Wert von Xvariiert je nach X.
epsilon()ist nur die Differenz zwischen 1und dem nächsten Wert von 1.
Der Unterschied zwischen 0und dem nächsten Wert von 0ist nicht epsilon().

Stattdessen können Sie std::nextaftereinen doppelten Wert 0wie folgt vergleichen:

bool same(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

double someValue = ...
if (same (someValue, 0.0)) {
  someValue = 0.0;
}
Daniel Laügt
quelle
2

Ich denke, das hängt von der Präzision Ihres Computers ab. Schauen Sie sich diese Tabelle an : Sie können sehen, dass der Vergleich nicht gleichwertig ist, wenn Ihr Epsilon durch Doppel dargestellt wird, Ihre Genauigkeit jedoch höher ist

someValue == 0.0

Gute Frage trotzdem!

Luca Davanzo
quelle
2

Sie können dies aufgrund von Mantissen- und Exponententeilen nicht auf 0 anwenden. Aufgrund des Exponenten können Sie sehr kleine Zahlen speichern, die kleiner als epsilon sind. Wenn Sie jedoch versuchen, etwas wie (1.0 - "sehr kleine Zahl") zu tun, erhalten Sie 1.0. Epsilon ist ein Indikator nicht für den Wert, sondern für die Wertgenauigkeit, die in Mantisse angegeben ist. Es zeigt, wie viele korrekte nachfolgende Dezimalstellen der Zahl wir speichern können.

Arsenii Fomin
quelle
2

Beim IEEE-Gleitkomma gibt es zwischen dem kleinsten positiven Wert ungleich Null und dem kleinsten negativen Wert ungleich Null zwei Werte: positive Null und negative Null. Das Testen, ob ein Wert zwischen den kleinsten Werten ungleich Null liegt, entspricht dem Testen der Gleichheit mit Null. Die Zuweisung kann jedoch Auswirkungen haben, da sie eine negative Null in eine positive Null ändern würde.

Es wäre denkbar, dass ein Gleitkommaformat drei Werte zwischen den kleinsten endlichen positiven und negativen Werten aufweist: positive infinitesimale, vorzeichenlose Null und negative infinitesimale. Ich kenne keine Gleitkommaformate, die tatsächlich so funktionieren, aber ein solches Verhalten wäre durchaus vernünftig und wohl besser als das von IEEE (vielleicht nicht besser genug, um zusätzliche Hardware zur Unterstützung hinzuzufügen, aber mathematisch 1 / (1 / INF), 1 / (- 1 / INF) und 1 / (1-1) sollten drei verschiedene Fälle darstellen, die drei verschiedene Nullen darstellen. Ich weiß nicht, ob ein C-Standard vorschreiben würde, dass signierte Infinitesimale, falls vorhanden, gleich Null sein müssten. Wenn dies nicht der Fall ist, könnte Code wie der oben genannte sicherstellen, dass z

Superkatze
quelle
Ist "1 / (1-1)" (aus Ihrem Beispiel) nicht unendlich statt Null?
Sebastian Krysmanski
Die Größen (1-1), (1 / INF) und (-1 / INF) stellen alle Null dar, aber das Teilen einer positiven Zahl durch jede von ihnen sollte theoretisch drei verschiedene Ergebnisse ergeben (IEEE-Mathematik betrachtet die ersten beiden als identisch ).
Supercat
1

Nehmen wir also an, das System kann 1.000000000000000000000 und 1.000000000000000000001 nicht unterscheiden. das ist 1,0 und 1,0 + 1e-20. Denken Sie, dass es noch einige Werte gibt, die zwischen -1e-20 und + 1e-20 dargestellt werden können?

Cababunga
quelle
Mit Ausnahme von Null glaube ich nicht, dass es Werte zwischen -1e-20 und + 1e-20 gibt. Aber nur weil ich denke, das macht es nicht wahr.
Sebastian Krysmanski
@SebastianKrysmanski: Es ist nicht wahr, es gibt viele Gleitkommawerte zwischen 0 und epsilon. Weil es Gleitkomma ist, nicht Fixpunkt.
Steve Jessop
Der kleinste darstellbare Wert, der sich von Null unterscheidet, ist durch die Anzahl der Bits begrenzt, die zur Darstellung des Exponenten zugewiesen sind. Wenn double also einen 11-Bit-Exponenten hat, wäre die kleinste Zahl 1e-1023.
Cababunga
0

Auch ein guter Grund für eine solche Funktion ist auch das Entfernen von "Denormals" (jene sehr kleinen Zahlen, die die implizierte führende "1" nicht mehr verwenden können und eine spezielle FP-Darstellung haben). Warum willst du das tun? Weil einige Maschinen (insbesondere einige ältere Pentium 4) bei der Verarbeitung von Denormals sehr, sehr langsam werden. Andere werden nur etwas langsamer. Wenn Ihre Anwendung diese sehr kleinen Zahlen nicht wirklich benötigt, ist es eine gute Lösung, sie auf Null zu setzen. Gute Orte, um dies zu berücksichtigen, sind die letzten Schritte von IIR-Filtern oder Abklingfunktionen.

Siehe auch: Warum verlangsamt das Ändern von 0.1f auf 0 die Leistung um das 10-fache?

und http://en.wikipedia.org/wiki/Denormal_number

Dithermaster
quelle
1
Dies entfernt viel mehr Zahlen als nur denormalisierte Zahlen. Es ändert die Plancksche Konstante oder die Masse eines Elektrons auf Null, was zu sehr, sehr falschen Ergebnissen führt, wenn Sie diese Zahlen verwenden.
Gnasher729