Rundungsfehler in .NETs Double.ToString-Methode

Betrachten Sie für diese Frage mathematisch die rationale Zahl

8725724278030350 / 2**48

woher** im Nenner bedeutet Exponentiation, d. h. der Nenner ist2 zum48th macht. (Der Bruch ist nicht in niedrigsten Begriffen, er kann um 2 reduziert werden.) Diese Zahl istgenau darstellbar alsSystem.Double. Ihre Dezimalerweiterung ist

31.0000000000000'49'73799150320701301097869873046875 (exact)

wobei die Apostrophe keine fehlenden Ziffern darstellen, sondern lediglich die Grenzen markieren, auf die gerundet wirdfünfzehn bzw.17 Stellen ist durchzuführen.

Beachten Sie Folgendes: Wenn diese Zahl auf 15 Stellen gerundet ist, ist das Ergebnis31 (gefolgt von dreizehn0s) weil die nächsten Ziffern (49...) beginnen mit a4 (bedeutet rundNieder). Aber wenn die Nummer istzuerst auf 17 Stellen gerundet unddann Auf 15 Stellen gerundet könnte das Ergebnis sein31.0000000000001. Dies liegt daran, dass die erste Aufrundung durch Erhöhen des49... Ziffern bis50 (terminates) (Die nächsten Ziffern waren73...), und die zweite Rundung wird möglicherweise erneut aufgerundet (wenn die Rundungsregel für den Mittelpunkt "von Null abrunden" lautet).

(Es gibt natürlich viel mehr Zahlen mit den oben genannten Merkmalen.)

Nun stellt sich heraus, dass .NET die Standard-Zeichenfolgendarstellung dieser Zahl ist"31.0000000000001". Die Frage: Ist das nicht ein Bug? Mit Standard-Stringdarstellung ist das gemeintString erzeugt durch die parameterlesDouble.ToString() Instanzmethode, die natürlich mit der identisch ist, die von erzeugt wirdToString("G").

Eine interessante Sache zu beachten ist, dass, wenn Sie die oben genannte Zahl zu werfenSystem.Decimal dann bekommst du adecimal das ist31 genau! SehenDiese Stapelüberlauf-Frage für eine Diskussion der überraschenden Tatsache, dass Casting aDouble zuDecimal Dabei wird zunächst auf 15 Stellen gerundet. Dies bedeutet, dass Casting zuDecimal macht eine korrekte Runde auf 15 Stellen, während der AufrufToSting() macht einen falschen.

Zusammenfassend haben wir eine Gleitkommazahl, die bei der Ausgabe an den Benutzer gleich ist31.0000000000001, aber wenn konvertiert zuDecimal (woher29 Ziffern stehen zur Verfügung), wird31 genau. Das ist unglücklich.

Hier ist ein C # -Code, mit dem Sie das Problem überprüfen können:

static void Main()
{
  const double evil = 31.0000000000000497;
  string exactString = DoubleConverter.ToExactString(evil); // Jon Skeet, http://csharpindepth.com/Articles/General/FloatingPoint.aspx 

  Console.WriteLine("Exact value (Jon Skeet): {0}", exactString);   // writes 31.00000000000004973799150320701301097869873046875
  Console.WriteLine("General format (G): {0}", evil);               // writes 31.0000000000001
  Console.WriteLine("Round-trip format (R): {0:R}", evil);          // writes 31.00000000000005

  Console.WriteLine();
  Console.WriteLine("Binary repr.: {0}", String.Join(", ", BitConverter.GetBytes(evil).Select(b => "0x" + b.ToString("X2"))));

  Console.WriteLine();
  decimal converted = (decimal)evil;
  Console.WriteLine("Decimal version: {0}", converted);             // writes 31
  decimal preciseDecimal = decimal.Parse(exactString, CultureInfo.InvariantCulture);
  Console.WriteLine("Better decimal: {0}", preciseDecimal);         // writes 31.000000000000049737991503207
}

Der obige Code verwendet SkeetsToExactString Methode. Wenn Sie sein Material nicht verwenden möchten (kann über die URL gefunden werden), löschen Sie einfach die obigen Codezeilen abhängig vonexactString. Sie können immer noch sehen, wie dieDouble fraglich (evil) ist abgerundet und gegossen.

ZUSATZ:

OK, also habe ich noch ein paar Zahlen getestet und hier ist eine Tabelle:

  exact value (truncated)       "R" format         "G" format     decimal cast
 -------------------------  ------------------  ----------------  ------------
 6.00000000000000'53'29...  6.0000000000000053  6.00000000000001  6
 9.00000000000000'53'29...  9.0000000000000053  9.00000000000001  9
 30.0000000000000'49'73...  30.00000000000005   30.0000000000001  30
 50.0000000000000'49'73...  50.00000000000005   50.0000000000001  50
 200.000000000000'51'15...  200.00000000000051  200.000000000001  200
 500.000000000000'51'15...  500.00000000000051  500.000000000001  500
 1020.00000000000'50'02...  1020.000000000005   1020.00000000001  1020
 2000.00000000000'50'02...  2000.000000000005   2000.00000000001  2000
 3000.00000000000'50'02...  3000.000000000005   3000.00000000001  3000
 9000.00000000000'54'56...  9000.0000000000055  9000.00000000001  9000
 20000.0000000000'50'93...  20000.000000000051  20000.0000000001  20000
 50000.0000000000'50'93...  50000.000000000051  50000.0000000001  50000
 500000.000000000'52'38...  500000.00000000052  500000.000000001  500000
 1020000.00000000'50'05...  1020000.000000005   1020000.00000001  1020000

Die erste Spalte gibt den exakten (wenn auch abgeschnittenen) Wert an, den derDouble vertreten. Die zweite Spalte enthält die Zeichenfolgendarstellung aus dem"R" Zeichenfolge formatieren. Die dritte Spalte enthält die übliche Zeichenfolgendarstellung. Und schließlich gibt die vierte Spalte dieSystem.Decimal das ergibt sich aus der Konvertierung diesesDouble.

Wir schließen folgendes:

Runden Sie auf 15 Stellen vonToString() und durch Umrechnung in auf 15 Stellen rundenDecimal stimme in sehr vielen Fällen nicht zuUmstellung aufDecimal runden auch in vielen Fällen falsch, und die Fehler in diesen Fällen können nicht als "Rundungsfehler" beschrieben werdenIn meinen FällenToString() scheint eine größere Zahl als zu ergebenDecimal Umwandlung, wenn sie nicht übereinstimmen (egal welche der beiden Runden richtig ist)

Ich habe nur mit solchen Fällen experimentiert. Ich habe nicht überprüft, ob Rundungsfehler mit Zahlen anderer "Formulare" vorliegen.

Antworten auf die Frage(4)

Ihre Antwort auf die Frage