Frage

Was ist die beste Methode zum Vergleich von IEEE-Floats und Doubles auf Gleichheit?Ich habe von mehreren Methoden gehört, wollte aber wissen, was die Community davon hält.

War es hilfreich?

Lösung

Der beste Ansatz ist meiner Meinung nach ein Vergleich ULPs.

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

Eine ähnliche Technik kann für Doppel verwendet werden.Der Trick besteht darin, die Gleitkommazahlen so umzuwandeln, dass sie geordnet sind (wie Ganzzahlen) und dann einfach zu sehen, wie unterschiedlich sie sind.

Ich habe keine Ahnung, warum dieses verdammte Ding meine Unterstriche durcheinander bringt.Bearbeiten:Oh, vielleicht ist das nur ein Artefakt der Vorschau.Dann ist das in Ordnung.

Andere Tipps

Die aktuelle Version, die ich verwende, ist diese

bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

Dies scheint die meisten Probleme zu lösen, indem relative und absolute Fehlertoleranz kombiniert werden.Ist der ULP-Ansatz besser?Wenn ja warum?

@DrPizza:Ich bin kein Performance-Guru, würde aber erwarten, dass Festkommaoperationen (in den meisten Fällen) schneller sind als Gleitkommaoperationen.

Es kommt eher darauf an, was man mit ihnen macht.Ein Festkommatyp mit dem gleichen Bereich wie ein IEEE-Float wäre um ein Vielfaches langsamer (und um ein Vielfaches größer).

Für Schwimmkörper geeignete Dinge:

3D-Grafik, Physik/Ingenieurwesen, Simulation, Klimasimulation....

In numerischer Software möchte man oft testen, ob zwei Gleitkommazahlen gleich sind genau gleich.LAPACK ist voller Beispiele für solche Fälle.Sicher, der häufigste Fall ist, dass Sie testen möchten, ob eine Gleitkommazahl gleich „Null“, „Eins“, „Zwei“ oder „Halb“ ist.Wenn jemand Interesse hat, kann ich einige Algorithmen auswählen und detaillierter darauf eingehen.

Auch in BLAS möchte man oft prüfen, ob eine Gleitkommazahl genau Null oder Eins ist.Beispielsweise kann die Routine dgemv Operationen der Form berechnen

  • y = beta*y + alpha*A*x
  • y = beta*y + alpha*A^T*x
  • y = beta*y + alpha*A^H*x

Wenn also Beta gleich Eins ist, haben Sie eine „Plus-Zuweisung“ und für Beta gleich Null eine „einfache Zuweisung“.Sie können den Rechenaufwand also durchaus senken, wenn Sie diesen (häufigen) Fällen eine besondere Behandlung zukommen lassen.

Natürlich könnten Sie die BLAS-Routinen so gestalten, dass Sie genaue Vergleiche vermeiden können (z. B.unter Verwendung einiger Flags).Allerdings gibt es im LAPACK viele Beispiele, bei denen dies nicht möglich ist.

P.S.:

  • Es gibt sicherlich viele Fälle, in denen Sie nicht prüfen möchten, ob „genau gleich“ ist.Für viele Menschen ist dies möglicherweise sogar der einzige Fall, mit dem sie sich jemals befassen müssen.Ich möchte nur darauf hinweisen, dass es auch andere Fälle gibt.

  • Obwohl LAPACK in Fortran geschrieben ist, ist die Logik dieselbe, wenn Sie andere Programmiersprachen für numerische Software verwenden.

Oh mein Gott, bitte interpretieren Sie die Float-Bits nicht als Ints, es sei denn, Sie verwenden ein P6 oder früher.

Selbst wenn es dazu führt, dass es über den Speicher von Vektorregistern in Ganzzahlregister kopiert, und selbst wenn es die Pipeline blockiert, ist es die beste Methode, die mir begegnet ist, insofern sie selbst auf den ersten Blick die robustesten Vergleiche liefert von Gleitkommafehlern.

d.h.Es ist ein Preis, den es wert ist, gezahlt zu werden.

Dies scheint die meisten Probleme zu lösen, indem relative und absolute Fehlertoleranz kombiniert werden.Ist der ULP-Ansatz besser?Wenn ja warum?

ULPs sind ein direktes Maß für den „Abstand“ zwischen zwei Gleitkommazahlen.Das bedeutet, dass Sie weder die relativen und absoluten Fehlerwerte heraufbeschwören müssen, noch müssen Sie sicherstellen, dass diese Werte „ungefähr richtig“ sind.Mit ULPs können Sie direkt ausdrücken, wie nahe die Zahlen beieinander liegen sollen, und derselbe Schwellenwert funktioniert für kleine Werte genauso gut wie für große.

Wenn Sie Gleitkommafehler haben, haben Sie sogar noch mehr Probleme.Obwohl ich denke, dass es an der persönlichen Perspektive liegt.

Selbst wenn wir die numerische Analyse durchführen, um die Anhäufung von Fehlern zu minimieren, können wir sie nicht beseitigen und es kann zu Ergebnissen kommen, die identisch sein sollten (wenn wir mit reellen Zahlen rechnen würden), sich aber unterscheiden (weil wir nicht mit reellen Zahlen rechnen können).

Wenn Sie darauf achten, dass zwei Floats gleich sind, dann sollten sie meiner Meinung nach identisch gleich sein.Wenn Sie mit einem Gleitkomma-Rundungsproblem konfrontiert sind, wäre eine Festkomma-Darstellung möglicherweise besser für Ihr Problem geeignet.

Wenn Sie darauf achten, dass zwei Floats gleich sind, dann sollten sie meiner Meinung nach identisch gleich sein.Wenn Sie mit einem Gleitkomma-Rundungsproblem konfrontiert sind, wäre eine Festkomma-Darstellung möglicherweise besser für Ihr Problem geeignet.

Vielleicht können wir uns den Reichweiten- oder Leistungsverlust, den ein solcher Ansatz mit sich bringen würde, nicht leisten.

@DrPizza:Ich bin kein Performance-Guru, würde aber erwarten, dass Festkommaoperationen (in den meisten Fällen) schneller sind als Gleitkommaoperationen.

@Craig H:Sicher.Ich bin völlig damit einverstanden, das zu drucken.Wenn a oder b Geld speichern, sollten sie in Festkommazahlen dargestellt werden.Es fällt mir schwer, mir ein Beispiel aus der Praxis vorzustellen, bei dem eine solche Logik mit Floats verbunden sein sollte.Für Schwimmkörper geeignete Dinge:

  • Gewichte
  • Ränge
  • Entfernungen
  • reale Werte (wie von einem ADC)

Für all diese Dinge verwenden Sie entweder viele Zahlen und präsentieren die Ergebnisse einfach dem Benutzer zur menschlichen Interpretation, oder Sie machen eine vergleichende Aussage (selbst wenn eine solche Aussage lautet: „Dieses Ding liegt innerhalb von 0,001 von diesem anderen Ding“).Eine vergleichende Aussage wie meine ist nur im Kontext des Algorithmus nützlich:Der Teil „innerhalb von 0,001“ hängt davon ab, was körperlich Frage, die Sie stellen.Das sind meine 0,02.Oder sollte ich 2/100stel sagen?

Es hängt eher davon ab, was Sie mit ihnen tun.Ein Festpunkttyp mit dem gleichen Bereich wie ein IEEE-Schwimmer wäre um ein Vielfaches langsamer (und um ein Vielfaches größer).

Okay, aber wenn ich eine unendlich kleine Bit-Auflösung möchte, dann bin ich wieder bei meinem ursprünglichen Punkt:== und != haben im Kontext eines solchen Problems keine Bedeutung.

Mit einem int kann ich ~10^9 Werte ausdrücken (unabhängig vom Bereich), was für jede Situation ausreichend zu sein scheint, in der mir wichtig wäre, dass zwei davon gleich sind.Und wenn das nicht ausreicht, verwenden Sie ein 64-Bit-Betriebssystem und Sie haben etwa 10^19 unterschiedliche Werte.

Ich kann Werte im Bereich von 0 bis 10^200 (zum Beispiel) in einem int ausdrücken, es ist nur die Bitauflösung, die darunter leidet (die Auflösung wäre größer als 1, aber wiederum hat keine Anwendung auch einen solchen Bereich als diese Art von Auflösung).

Zusammenfassend denke ich, dass man in allen Fällen entweder ein Kontinuum von Werten darstellt, wobei != und == irrelevant sind, oder man einen festen Satz von Werten darstellt, der einem int (oder einem anderen festen Wert) zugeordnet werden kann -Präzisionstyp).

Mit einem int kann ich ~ 10^9 Werte (unabhängig vom Bereich) ausdrücken, was für jede Situation genug ausreicht, in der ich mich um zwei von ihnen kümmern würde.Und wenn das nicht genug ist, verwenden Sie ein 64-Bit-Betriebssystem und Sie haben ungefähr 10^19 verschiedene Werte.

Ich habe diese Grenze tatsächlich erreicht ...Ich habe versucht, Zeiten in ps und Zeit in Taktzyklen in einer Simulation unter einen Hut zu bringen, bei der man problemlos 10^10 Zyklen erreicht.Egal, was ich getan habe, ich habe sehr schnell den mickrigen Bereich der 64-Bit-Ganzzahlen überschritten ...10^19 ist nicht so viel, wie Sie denken, geben Sie mir jetzt 128-Bit-Rechner!

Floats ermöglichten es mir, eine Lösung für die mathematischen Probleme zu finden, da die Werte am unteren Ende mit vielen Nullen überliefen.Sie hatten also im Grunde genommen einen Dezimalpunkt, der in der Zahl ohne Genauigkeitsverlust umherschwebte (ich könnte mir die im Vergleich zu einem 64-Bit-Ganzzahlzeichen begrenztere eindeutige Anzahl von Werten in der Mantisse eines Gleitkommas wünschen, brauchte diesen Bereich aber dringend!). ).

Und dann werden die Dinge zum Vergleich wieder in ganze Zahlen umgewandelt usw.

Ärgerlich, und am Ende habe ich den gesamten Versuch verworfen und mich nur auf Floats und < und > verlassen, um die Arbeit zu erledigen.Nicht perfekt, funktioniert aber für den vorgesehenen Anwendungsfall.

Wenn Sie darauf achten, dass zwei Floats gleich sind, dann sollten sie meiner Meinung nach identisch gleich sein.Wenn Sie mit einem Gleitkomma-Rundungsproblem konfrontiert sind, wäre eine Festkomma-Darstellung möglicherweise besser für Ihr Problem geeignet.

Vielleicht sollte ich das Problem besser erklären.In C++ der folgende Code:

#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

gibt den Satz „Etwas stimmt nicht“ aus.Wollen Sie damit sagen, dass es so sein sollte?

Oh mein Gott, bitte interpretieren Sie die Float-Bits nicht als Ints, es sei denn, Sie verwenden ein P6 oder früher.

Dies ist die beste Methode, die mir begegnet ist, da sie selbst bei Gleitkommafehlern die zuverlässigsten Vergleiche liefert.

Wenn Sie Gleitkommafehler haben, haben Sie sogar noch mehr Probleme.Obwohl ich denke, dass es an der persönlichen Perspektive liegt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top