Laden...

Diskussion zu: Gleichheit von Gleitkommazahlen (inkl. etwas Theorie)

Erstellt von T-Man vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.953 Views
Hinweis von winSharp93 vor 12 Jahren

Diskussionen zu Gleichheit von Gleitkommazahlen (inkl. etwas Theorie)

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

Sollte nicht die Prüfung des absoluten Fehlers nur dann erfolgen, wenn es sich um Zahlen verschiedenen Vorzeichens handelt?

Sonst werden besonders kleine Zahlen Grundsätzlich als ähnlich angesehen, selbst wenn sie relativ gesehen sehr unterschiedlich sind.

Hier mein Vorschlag:


        public static readonly double Epsilon = Math.Pow(2d, -52d);

        public static bool IsEqualTo(this double value, double other) { return IsEqualTo(value, other, Epsilon); }

        public static bool IsEqualTo(this double value, double other, double absEps)
        {
            var specialCase = CheckSpecialCases(value, other);
            if (specialCase.HasValue) return specialCase.Value;
            if (value >= .0)
            {
                if (other >= .0)
                    return value > other
                        ? (value - other) / value < Epsilon
                        : (other - value) / other < Epsilon;
            }
            else if (other < .0)
                return value > other
                    ? (other - value) / other < Epsilon
                    : (value - other) / value < Epsilon;
            return Math.Abs(value - other) < absEps;
        }

        private static bool? CheckSpecialCases(double value, double other)
        {
            if (value == other) return true;
            if (double.IsNaN(value)) return double.IsNaN(other);
            if (double.IsNegativeInfinity(value)) return double.IsNegativeInfinity(other);
            if (double.IsPositiveInfinity(value)) return double.IsPositiveInfinity(other);
            if (double.IsNaN(other) || double.IsInfinity(other)) return false;
            return null;
        }

Gruß
T-Man

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

Bei der zweiten Variante (Vergleich der signifikanten Stellen) vermute ich ein Problem:
Wenn wir mit Zahlen mit extremen Exponenten arbeiten, können wir sie nach der Multiplikation noch immer nicht in long wandeln. Müssen wir nicht zunächst den Exponenten wegbekommen und erst danach multiplizieren?

T-Man

U
1.688 Beiträge seit 2007
vor 12 Jahren

Hallo,

Hier mein Vorschlag:

Bei Dir ist, z. B., -10 == 10 - das kann so nicht stimmen...

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

Hallo,

Hier mein Vorschlag:

Bei Dir ist, z. B., -10 == 10 - das kann so nicht stimmen...

Autsch. Es muss natürlich

return Math.Abs(value - other) < absEps;

und nicht

return Math.Abs(value + other) < absEps;

sein.

Danke! Werde es korrigieren.

U
1.688 Beiträge seit 2007
vor 12 Jahren

Hallo,

in welchem Fall genau unterscheidet sich Deine Lösung von der gfoidls?

Machen Deine vielen Fallunterscheidungen nicht genau das gleiche? Math.Abs ist dann doch übersichtlicher.

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

gfoidl prüft immer zuerst den absoluten Fehler.

Das ist mMn nicht richtig. Diese Prüfung ist nur bei Zahlen verschieden Vorzeichens sinnvoll. gfoidl hat sie ja genau zu dem Zweck eingebaut, dass zwei Zahlen die näherungsweise 0 sind aber sich im Vorzeichen unterscheiden als gleich angesehen werden können.

Wenn man mit gfoidls Methode zwei Zahlen gleichen Vorzeichens mit einem Absolutbetrag kleiner eps_a vergleicht werden sie immer als gleich angesehen. Auch wenn der relative Unterschied sehr groß ist.

Wenn man das verhindern möchte, muss man die Vorzeichen prüfen. Und wenn man das schonmal tut, muss man nicht mehr Abs verwenden.

Die Methode muss auch nicht unbedingt 'übersichtlich' sein. Wichtiger ist doch, dass sie korrekt und möglichst performant ist.

Gruß
T-Man

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

Eine wichtige Frage, die man sich stellen muss ist, ob man wirklich eine sehr kleine negative und eine sehr kleine positive Zahl als gleich sehen möchte. Das hängt stark vom Kontext ab.
Wenn man dies nicht möchte, sollte man für absEps bzw. eps_a double.Epsilon setzen wenn man wenigstens -0 und 0 als gleich ansehen möchte.
Soll sogar -0 und 0 als verschieden angesehen werden, setzt man 0 für absEps ein.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo T-Man,

man kann da geteilter Meinung sein. Nimm diesen Code:

double d1 = 0.1f;
double d2 = 0.001f;
double d3 = 1 - d1*10;
double d4 = 1 - d2*1000;
Console.WriteLine (d3);
Console.WriteLine (d4);

Die Ausgabe ist

-1,49011611938477E-08
-4,74974513053894E-08

beides kleine Zahlen mit gleichem Vorzeichen und einem ziemlichen großen relativen Fehler, gerade weil die (absoluten) Zahlen so klein sind. Ohne Rundungsfehler käme in beiden Fällen Null (0) heraus. Man würde sich also hier erhoffen, dass IsEqualsTo true liefert. Tut deine Implementierung aber gerade nicht. Der absolute Fehler kann also durchaus eine Rolle spielen, nicht nur wenn das Vorzeichen unterschiedlich ist.

herbivore

PS: Unsere Antworten haben sich überschnitten. Meine bezieht sich auf deinen Beitrag davor. Kontext ist der richtige Begriff. Es gibt hier kein pauschales Richtig oder Falsch, sondern es hängt vom Kontext ab, was gewünscht ist.

T
T-Man Themenstarter:in
210 Beiträge seit 2006
vor 12 Jahren

Guter Einwand. Das zeigt nochmal, dass man genau wissen muss, was man eigentlich möchte.
gfoidls Metode liefert übrigens auch nur dann true zurück, wenn man das eps_a entsprechend groß wählt.

Hinweis von Abt vor 12 Jahren

Bitte keine Full-Quotes.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo,

kurze Erklärung:
Das Snippet werde ich dann anpassen/ändern, wenn ein Testfall zeigt dass es ein falsches Verhalten besitzt und das nicht auf einen "Bedienfehler" zurückzuführen ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"