Frage

ein schnelles Experiments Laufen im Zusammenhang href="https://stackoverflow.com/questions/1420752/is-double-multiplication-broken-in-net"> zum und ein paar Artikel über C # String-Formatierung lesen, dachte ich, dass dies:

{
    double i = 10 * 0.69;
    Console.WriteLine(i);
    Console.WriteLine(String.Format("  {0:F20}", i));
    Console.WriteLine(String.Format("+ {0:F20}", 6.9 - i));
    Console.WriteLine(String.Format("= {0:F20}", 6.9));
}

Kann die C # Äquivalent dieser C-Code sein:

{
    double i = 10 * 0.69;

    printf ( "%f\n", i );
    printf ( "  %.20f\n", i );
    printf ( "+ %.20f\n", 6.9 - i );
    printf ( "= %.20f\n", 6.9 );
}

Doch die C # erzeugt die Ausgabe:

6.9
  6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000

trotz i auf den Wert zeigt sich gleich 6,89999999999999946709 (statt 6,9) im Debugger.

im Vergleich zu C, die die Genauigkeit durch das Format angefordert zeigen:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527          

Was ist los?

(Microsoft .NET Framework Version 3.51 SP1 / Visual Studio C # 2008 Express Edition)


Ich habe einen Hintergrund in numerischen Berechnungen und Erfahrungen Intervallarithmetik Implementierung - eine Technik für die Fehler aufgrund der Grenzen der Präzision bei komplizierten numerischen Systemen Schätzung - auf verschiedene Plattformen. Um die Prämie zu erhalten, versuchen Sie nicht, und erklären, über die Lagerung Präzision - in diesem Fall ist es ein Unterschied von einem ULP eines 64-Bit-Doppel.

, um die Prämie zu erhalten, möchte ich wissen, wie (oder ob) .Net eine doppelte auf die gewünschte Präzision zu formatieren kann als sichtbar im C-Code.

War es hilfreich?

Lösung

Das Problem ist, dass .NET wird immer um einen double bis 15 signifikante Dezimalstellen vor Anwendung Ihre Formatierung, unabhängig von der Präzision von Ihrem Format angefordert und unabhängig von der exakten Dezimalwert der binären Zahl .

Ich würde vermuten, dass das Visual Studio-Debugger eine eigenen Format / Display-Routinen hat, die direkt die interne binäre Zahl zuzugreifen, damit die Unterschiede zwischen C # -Code, Ihrem C-Code und dem Debugger.

Es gibt nichts eingebaute, dass ermöglicht es Ihnen, die genaue Dezimalwert eines double zuzugreifen, oder Sie zu ermöglichen, eine double auf eine bestimmte Anzahl von Dezimalstellen zu formatieren, aber man konnte durch zerpflücken die interne binäre dies selbst tun Anzahl und als String-Darstellung des Dezimalwerts wieder aufzubauen.

Alternativ können Sie auch DoubleConverter Klasse Jon Skeet (verlinkt nutzen könnten, um von seinem "Binary Floating-Point-und .NET" Artikel ). Dies hat eine ToExactString Verfahren, das die genaue Dezimalwert eines double zurückgibt. Sie könnten dies leicht ändern Abrundung der Ausgabe auf eine bestimmte Genauigkeit zu ermöglichen.

double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));

// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625

Andere Tipps

Digits after decimal point
// just two decimal places
String.Format("{0:0.00}", 123.4567);      // "123.46"
String.Format("{0:0.00}", 123.4);         // "123.40"
String.Format("{0:0.00}", 123.0);         // "123.00"

// max. two decimal places
String.Format("{0:0.##}", 123.4567);      // "123.46"
String.Format("{0:0.##}", 123.4);         // "123.4"
String.Format("{0:0.##}", 123.0);         // "123"
// at least two digits before decimal point
String.Format("{0:00.0}", 123.4567);      // "123.5"
String.Format("{0:00.0}", 23.4567);       // "23.5"
String.Format("{0:00.0}", 3.4567);        // "03.5"
String.Format("{0:00.0}", -3.4567);       // "-03.5"

Thousands separator
String.Format("{0:0,0.0}", 12345.67);     // "12,345.7"
String.Format("{0:0,0}", 12345.67);       // "12,346"

Zero
Following code shows how can be formatted a zero (of double type).

String.Format("{0:0.0}", 0.0);            // "0.0"
String.Format("{0:0.#}", 0.0);            // "0"
String.Format("{0:#.0}", 0.0);            // ".0"
String.Format("{0:#.#}", 0.0);            // ""

Align numbers with spaces
String.Format("{0,10:0.0}", 123.4567);    // "     123.5"
String.Format("{0,-10:0.0}", 123.4567);   // "123.5     "
String.Format("{0,10:0.0}", -123.4567);   // "    -123.5"
String.Format("{0,-10:0.0}", -123.4567);  // "-123.5    "

Custom formatting for negative numbers and zero
String.Format("{0:0.00;minus 0.00;zero}", 123.4567);   // "123.46"
String.Format("{0:0.00;minus 0.00;zero}", -123.4567);  // "minus 123.46"
String.Format("{0:0.00;minus 0.00;zero}", 0.0);        // "zero"

Some funny examples
String.Format("{0:my number is 0.0}", 12.3);   // "my number is 12.3"
String.Format("{0:0aaa.bbb0}", 12.3);

Werfen Sie einen Blick auf diese MSDN Referenz . In den Erläuterungen heißt es, dass die Zahlen auf die Anzahl der Dezimalstellen gerundet angefordert werden.

Wenn Sie stattdessen "{0: R}" wird es produzieren, was bezeichnet ist als "Round-Trip" Wert, werfen Sie einen Blick auf diese MSDN Referenz für weitere Informationen, hier ist mein Code und die Ausgabe:

double d = 10 * 0.69;
Console.WriteLine("  {0:R}", d);
Console.WriteLine("+ {0:F20}", 6.9 - d);
Console.WriteLine("= {0:F20}", 6.9);

Ausgang

  6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000

Obwohl diese Frage inzwischen geschlossen ist, ich glaube, es ist erwähnenswert, wie diese Gräueltat entstanden. In gewisser Weise können Sie den C # spec, die Schuld, die besagt, dass eine doppelte eine Genauigkeit von 15 oder 16 Ziffern haben müssen (das Ergebnis der IEEE-754). Ein Stück weiter (Abschnitt 4.1.6) es wird gesagt, dass Implementierungen verwenden darf höher Präzision. Wohlgemerkt: höher , nicht geringer. Ausdrücke des Typs x * y / z wo x * y +/-INF ergeben würde aber in einem gültigen Bereich wäre nach dem Teilen, müssen nicht zu einem Fehler: Sie sind sogar abweichen von IEEE-754 erlaubt. Diese Funktion macht es einfacher für Compiler in Architekturen höhere Präzision zu verwenden, wo die bessere Leistung ergeben würde.

Aber ich versprechen einen „Grund“. Hier ist ein Zitat (Sie bat um eine Ressource in einem Ihrer letzten Kommentare) von der Shared Source CLI , in clr/src/vm/comnumber.cpp:

  

"Um Zahlen zu geben, die beide   freundlich anzuzeigen und   Rund-um-auslösbare, analysieren wir die Zahl   15 Ziffern und dann bestimmen, ob   es Rundfahrten auf den gleichen Wert. Wenn   ja, wir diese Zahl auf eine konvertieren   string, sonst Analysepunkte wir 17 unter Verwendung von   Ziffern und angezeigt werden. "

Mit anderen Worten: MS CLI Development Team entschied sowohl rund um auslösbare und zeigen ziemlich Werte zu sein, die nicht so ein Schmerz zu lesen sind. Gut oder schlecht? Ich würde für ein Opt-in möchten oder Opt-out.

Der Trick es tut, um diesen Rund trippability jede gegebene Zahl herausfinden? Die Umstellung auf eine generische NUMBER Struktur (die für die Eigenschaften eines Doppels getrennte Felder hat) und zurück, und dann vergleichen, ob das Ergebnis anders ist. Wenn es anders ist, wird der genaue Wert (wie in Ihrem mittleren Wert mit 6.9 - i) verwendet werden, wenn es das gleiche ist, wird der „ziemlich Wert“ verwendet.

Wie Sie bereits in einem Kommentar zu Andyp bemerkt, ist 6.90...00 bitweise gleich 6.89...9467. Und Sie wissen jetzt, warum 0.0...8818 verwendet wird. Es ist bitweise unterscheidet sich von 0.0

Das 15 Stellen Barriere ist hart codiert und kann nur durch erneutes Kompilieren des CLI geändert werden, indem man mit Mono oder von Microsoft aufrufen und dazu verleitet, eine Option hinzufügen voll „Präzision“ (um es zu drucken ist nicht wirklich Präzision, sondern durch den Mangel eines besseren Wortes). Es ist wahrscheinlich einfacher, nur die 52 Bit Genauigkeit selbst zu berechnen oder die Bibliothek bereits erwähnt.

EDIT: wenn Sie sich mit IEE-754 floating Punkte experimentieren, betrachten dieses Online-Tool , mit dem Sie alle relevanten Teile eines Gleitkomma zeigt.

Mit

Console.WriteLine(String.Format("  {0:G17}", i));

Das wird Ihnen alle 17 Ziffern haben es. Standardmäßig enthält ein doppelter Wert 15 Dezimalziffern Präzision, obwohl ein Maximum von 17 Ziffern intern gehalten wird. {0: R}. Wird nicht immer Ihnen 17 Ziffern gibt, wird es 15 geben, wenn die Zahl kann mit dieser Genauigkeit dargestellt wird

, die 15 Stellen zurückgibt, wenn die Zahl kann mit dieser Genauigkeit oder 17 Ziffern dargestellt werden, wenn die Zahl kann nur mit maximaler Präzision dargestellt werden. Es gibt nichts, was Sie tun können, die die doppelte Rückkehr mehr Stellen zu machen, die die Art und Weise ist es umgesetzt wird. Wenn Sie nicht mögen, es ist eine neue Doppel Klasse selbst tun ...

.NET double kippe speichern alle mehr Stellen als 17 so kippe Sie 6,89999999999999946709 im Debugger sehen Sie 6,8999999999999995 sehen würde. Bitte geben Sie ein Bild uns das Gegenteil zu beweisen.

Die Antwort ist einfach und kann auf MSDN gefunden werden

  

Beachten Sie, dass eine Fließkommazahl nur eine Dezimalzahl annähern kann, und dass die Genauigkeit einer Gleitkommazahl bestimmt, wie genau diese Zahl eine Dezimalzahl annähert. Standardmäßig wird ein Double-Wert enthält 15 Dezimalziffern Präzision , obwohl ein Maximum von 17 Ziffern intern gehalten wird.

In Ihrem Beispiel ist der Wert von i 6,89999999999999946709, die die Nummer 9 für alle Positionen zwischen dem 3. und dem 16. hat Ziffer (erinnere mich an den Integer-Teil in den Ziffern zu zählen). Wenn auf String-Umwandlung, rundet das Framework die Nummer 15. Ziffer.

i     = 6.89999999999999 946709
digit =           111111 111122
        1 23456789012345 678901

Ich habe versucht, Ihre Ergebnisse zu reproduzieren, aber als ich sah ‚i‘ im Debugger zeigte sich, als ‚6,8999999999999995‘ nicht als ‚6,89999999999999946709‘, wie Sie in der Frage geschrieben. Können Sie die Schritte zur Verfügung stellen zu reproduzieren, was du gesehen hast?

Um zu sehen, was der Debugger zeigt Ihnen, Sie eine DoubleConverter wie in der folgenden Codezeile verwenden können:

Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));

Hope, das hilft!

Edit:. Ich glaube, ich bin müde, als ich dachte, das ist natürlich gleich der Umlaufwert als die Formatierung (wie bereits erwähnt)

Die Antwort ist ja, Doppeldruck in .NET ist gebrochen, sie sind Druckabfall Ziffern Hinter.

Sie können lesen, wie es richtig zu implementieren hier .

Ich habe das gleiche für Ironscheme zu tun.

> (* 10.0 0.69)
6.8999999999999995
> 6.89999999999999946709
6.8999999999999995
> (- 6.9 (* 10.0 0.69))
8.881784197001252e-16
> 6.9
6.9
> (- 6.9 8.881784197001252e-16)
6.8999999999999995

. Hinweis: Sowohl C und C # richtigen Wert hat, nur gebrochen Druck

Update: Ich bin noch auf der Suche für das Mailing-Liste Gespräch, das ich, dass zu dieser Entdeckung führen musste

.

Ich fand diese schnelle Lösung.

    double i = 10 * 0.69;
    System.Diagnostics.Debug.WriteLine(i);


    String s = String.Format("{0:F20}", i).Substring(0,20);
    System.Diagnostics.Debug.WriteLine(s + " " +s.Length );

Console.WriteLine (string.Format ( "Kursgebühren sind {0: 0.00}", + cfees));

internal void DisplaycourseDetails()
        {
            Console.WriteLine("Course Code : " + cid);
            Console.WriteLine("Couse Name : " + cname);
            //Console.WriteLine("Couse Name : " + string.Format("{0:0.00}"+ cfees));
            // string s = string.Format("Course Fees is {0:0.00}", +cfees);
            // Console.WriteLine(s);
            Console.WriteLine( string.Format("Course Fees is {0:0.00}", +cfees));
        }
        static void Main(string[] args)
        {
            Course obj1 = new Course(101, "C# .net", 1100.00);
            obj1.DisplaycourseDetails();
            Course obj2 = new Course(102, "Angular", 7000.00);
            obj2.DisplaycourseDetails();
            Course obj3 = new Course(103, "MVC", 1100.00);
            obj3.DisplaycourseDetails();
            Console.ReadLine();
        }
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top