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?


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 ...


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.


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.


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;
}

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.


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;
}

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!


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.


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


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?


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


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.