myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » FAQ » [FAQ] Double und Float: Fehler beim Vergleich und Rundungsfehler
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[FAQ] Double und Float: Fehler beim Vergleich und Rundungsfehler

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
JAck30lena JAck30lena ist männlich
myCSharp.de-Team (Admin)

images/avatars/avatar-2653.jpg


Dabei seit: 01.10.2006
Beiträge: 11.394
Entwicklungsumgebung: Visual Studio 05/08/10 Prof.


JAck30lena ist offline

[FAQ] Double und Float: Fehler beim Vergleich und Rundungsfehler

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Einordnung

Die von C#/.NET verwendeten Gleitkommatypen float/Single und double/Double sind über CPU-Befehle realisiert und nach  IEEE 754 genormt. Diese Norm wird von den meisten CPUs und in der Folge von den meisten Programmiersprachen verwendet. Daher ist das Folgende kein spezifisches Problem von C#/.NET, sondern ein Problem des weit verbreiteten Standards bzw. genereller gesprochen der auf Computern zwangsläufig begrenzten Genauigkeit der Zahlendarstellung.



Das Repräsentationsphänomen

Betrachten wir folgenden Code:

C#-Code:
            double d2 = 0.1F;
            Console.WriteLine(d2);

Nun sollte die Ausgabe 0.1 sein. Wenn man nun aber den Code ausführt, so erhält man "0,100000001490112" als Ergebnis. Warum?
Das Problem ist, dass Gleitkommazahlen (float/double) eine endliche Genauigkeit haben. Binär betrachtet ergibt sich bei der Repräsentation von 0.1 eine Periodizität ( binär: 0,00011001100110011...), die aufgrund der Genauigkeit (32bit/64bit/80bit...) abgeschnitten wird. Beim Rückrechnen in das Dezimalsystem (für die Anzeige z.B.) entstehen somit Ungenauigkeiten.

Detailliertere Informationen findet man hier:
(deutsch) Rundungsfehler beim Umwandeln von Fließkommawerten in Ganzzahlwerte
(englisch) Five Tips for Floating Point Programming





Das Phänomen der Rundungsfehler

Betrachten wir folgenden Code:

C#-Code:
            float f = 1.11f;
            double d = 111f;

            for(int i = 0; i < 1000; i++)
            {
                float erg = ((float) d/100);
                if (erg != f)
                {
                    Console.WriteLine("fehler!");
                }
                    f = 1.11f;
                    d = 111f;
            }
            Console.WriteLine("Fertig");
            Console.ReadLine();

Generiert man einen Debug-Stand und lässt ihn laufen, dann tritt keinerlei Fehler auf. Im Release-Stand allerdings tritt ein Fehler auf, aber auch nicht auf allen Rechnern und auch nur, wenn man nicht direkt aus VS startet (also Optimierungen zulässt).
Das ist eine Art von Fehlerquelle, die den meisten Programmierern nicht bewusst ist.

Die Ursache liegt in der Art und Weise, wie Gleitkommazahlen auf der CPU behandelt werden und was der JIT daraus macht. Wenn der Code im Debugmode ist, dann sind dem JIT jegliche CPU Optimierungen untersagt. Selbiges passiert auch, wenn sich ein Debugger anhängt. Im Release-Stand allerdings, wenn kein Debugger angehängt ist, versucht der JIT, CPU-spezifische Optimierungen am Ausführungscode vorzunehmen, um die Performance zu steigern.

Im obigen Code liegt die Ursache darin, das der JIT im Debug-Mode veranlasst, das die Zahlen vor dem Vergleich gerundet werden. Das geschieht in der CPU, wenn man die Zahl aus dem FPU-Stack herausnimmt. (Für genauere Informationen siehe obigen ersten Link und dort relativ am Ende des Beitrags.)



Wie vergleicht man nun Gleitkommadatentypen?

C#-Code:
if (Math.Abs(floatzahl1 - floatzahl2) < EPSILON)
//EPSILON = 0.009

Hier nimmt man aber geflissentlich eine Ungenauigkeit in Kauf (das  Epsilon). Ein absolut sicheres Vergleichen (also absolut genaues) ist jedoch mit Gleitkommatypen (ohne zu runden) nicht möglich.
In manchen Fällen (bei größeren Berechnung z.B.), ist es notwendig, die Ungenauigkeit (Toleranz) des Vergleiches zu erhöhen (z.B. 2 * Epsilon), da praktisch gesehen jede Operation mit Gleitkommadatentypen die Ungenauigkeit verschlimmert (Ungenau + Ungenau = doppelt so Ungenau).

Auch bei "<" und ">" Vergleichen muss man diese Ungenauigkeit in Kauf nehmen und im Hinterkopf behalten, dass unter Umständen die Bedingung zutrifft oder nicht zutrifft, obwohl sie es sollte.

Allgemein sollte Epsilon (also die Toleranz) so klein wie möglich und zugleich so groß wie nötig gehalten werden.

Prinzipiell sollte man daher Vergleiche von Gleitkommatypen wenn möglich vermeiden.



Wie groß soll Epsilon konkret sein?

C#-Code:
        public static double Epsilon()
        {
            double tau = 1.0;
            double alt = 1.0;
            double neu = 0.0;

            while (neu != alt)
            {
                tau *= 0.5;
                neu = alt + tau;
            }

            return 2.0 * tau;
        }

 Quelle

Es empfiehlt sich, diesen Wert beim Start eines Programms nur einmal berechnen zu lassen (sofern notwendig) und das Ergebnis in einer statischen Variable zu halten.

Siehe auch:  float, double Arithmetik: Epsilon berechnen
_




Die Alternative

Eine Alternative zu float oder double ist  Decimal (96bit).

Decimal ist primär für finanzmathematische Berechnungen gedacht, wo Rundungsfehler äußerst unangenehm und sehr kritisch sind.

Bei Addition und Subtraktion innerhalb des Genauigkeitsbereiches (28 Stellen nach dem Komma), treten hierbei keine Ungenauigkeiten auf. Lediglich bei Multiplikation mit gebrochenen Zahlen und Division, muss man unter Umständen bei einer daraus resultierenden Periodizität eine Rundung durchführen, wobei hier das Epsilon sehr klein gehalten werden kann, da die Periode über der Genauigkeit einfach abgeschnitten wird.

C#-Code:
            Decimal d1 = 1;
            Decimal d2 = d1 / 3m * 3m; // ergibt 0.999999999999999999999....

Abschließendes: Der VB.NET Compiler baut automatisch zu Lasten der Performance Rundungen ein. In C# und C++ muss sich der Programmierer um das Runden selbst kümmern.


Siehe auch

 Gleichheit von Gleitkommazahlen (inkl. etwas Theorie)

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von JAck30lena am 19.11.2009 11:14.

28.11.2008 11:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 5 Jahre.
Der letzte Beitrag ist älter als 5 Jahre.
Antwort erstellen


© Copyright 2003-2014 myCSharp.de-Team. Alle Rechte vorbehalten. 25.07.2014 03:33