2010-02-12 13 views
18

Ich sehe, dass in C#, Rundung decimal, standardmäßig verwendet MidpointRounding.ToEven. Dies ist zu erwarten und genau das, was die C# -Spezifikation vorschreibt. Angesichts der folgenden:Warum wird .NET decimal.ToString (string) von Null weggerundet, scheinbar inkonsistent mit der Sprachspezifikation?

  • A decimal dVal
  • Ein Format string sFmt die, wenn sie in zu dVal.ToString(sFmt) weitergegeben, in einem String führt eine abgerundete Version von dVal

enthält, ... es ist offensichtlich, dass decimal.ToString(string) einen Wert zurückgibt, der mit MidpointRounding.AwayFromZero gerundet wurde. Dies scheint ein direkter Widerspruch zu der C# -Spezifikation zu sein.

Meine Frage ist dies: gibt es einen guten Grund, dass dies der Fall ist? Oder ist das nur eine Inkonsistenz in der Sprache?

Unten, als Referenz, habe ich einige Code enthalten, der eine Reihe von Rundungsoperation Ergebnisse und decimal.ToString(string) Betriebsergebnisse, jeder auf jedem Wert in einem Array von decimal Werte zu Konsole Konsolen schreibt. Die tatsächlichen Ausgaben sind eingebettet. Danach habe ich einen relevanten Absatz aus dem C# -Sprachspezifikationsabschnitt unter dem Typ decimal eingefügt.

Der Beispielcode:

static void Main(string[] args) 
{ 
    decimal[] dArr = new decimal[] { 12.345m, 12.355m }; 

    OutputBaseValues(dArr); 
    // Base values: 
    // d[0] = 12.345 
    // d[1] = 12.355 

    OutputRoundedValues(dArr); 
    // Rounding with default MidpointRounding: 
    // Math.Round(12.345, 2) => 12.34 
    // Math.Round(12.355, 2) => 12.36 
    // decimal.Round(12.345, 2) => 12.34 
    // decimal.Round(12.355, 2) => 12.36 

    OutputRoundedValues(dArr, MidpointRounding.ToEven); 
    // Rounding with mr = MidpointRounding.ToEven: 
    // Math.Round(12.345, 2, mr) => 12.34 
    // Math.Round(12.355, 2, mr) => 12.36 
    // decimal.Round(12.345, 2, mr) => 12.34 
    // decimal.Round(12.355, 2, mr) => 12.36 

    OutputRoundedValues(dArr, MidpointRounding.AwayFromZero); 
    // Rounding with mr = MidpointRounding.AwayFromZero: 
    // Math.Round(12.345, 2, mr) => 12.35 
    // Math.Round(12.355, 2, mr) => 12.36 
    // decimal.Round(12.345, 2, mr) => 12.35 
    // decimal.Round(12.355, 2, mr) => 12.36 

    OutputToStringFormatted(dArr, "N2"); 
    // decimal.ToString("N2"): 
    // 12.345.ToString("N2") => 12.35 
    // 12.355.ToString("N2") => 12.36 

    OutputToStringFormatted(dArr, "F2"); 
    // decimal.ToString("F2"): 
    // 12.345.ToString("F2") => 12.35 
    // 12.355.ToString("F2") => 12.36 

    OutputToStringFormatted(dArr, "###.##"); 
    // decimal.ToString("###.##"): 
    // 12.345.ToString("###.##") => 12.35 
    // 12.355.ToString("###.##") => 12.36 

    Console.ReadKey(); 
} 

private static void OutputBaseValues(decimal[] dArr) 
{ 
    Console.WriteLine("Base values:"); 
    for (int i = 0; i < dArr.Length; i++) Console.WriteLine("d[{0}] = {1}", i, dArr[i]); 
    Console.WriteLine(); 
} 

private static void OutputRoundedValues(decimal[] dArr) 
{ 
    Console.WriteLine("Rounding with default MidpointRounding:"); 
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2) => {1}", d, Math.Round(d, 2)); 
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2) => {1}", d, decimal.Round(d, 2)); 
    Console.WriteLine(); 
} 

private static void OutputRoundedValues(decimal[] dArr, MidpointRounding mr) 
{ 
    Console.WriteLine("Rounding with mr = MidpointRounding.{0}:", mr); 
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2, mr) => {1}", d, Math.Round(d, 2, mr)); 
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2, mr) => {1}", d, decimal.Round(d, 2, mr)); 
    Console.WriteLine(); 
} 

private static void OutputToStringFormatted(decimal[] dArr, string format) 
{ 
    Console.WriteLine("decimal.ToString(\"{0}\"):", format); 
    foreach (decimal d in dArr) Console.WriteLine("{0}.ToString(\"{1}\") => {2}", d, format, d.ToString(format)); 
    Console.WriteLine(); 
} 


Der Absatz von Abschnitt 4.1.7 der C# Language Specification ("Der Dezimal-Typ") (erhalten Sie die vollständige Spezifikation here (.doc)):

Das Ergebnis einer Operation für Werte vom Typ Dezimal ist derjenige, der sich aus der Berechnung eines exakten Ergebnisses ergeben würde (Beibehalten der Skalierung, wie für jeden Operator definiert) und anschließendes Runden, um die Darstellung anzupassen. Die Ergebnisse werden auf den nächsten darstellbaren Wert und, wenn ein Ergebnis gleich zwei darstellbaren Werten entspricht, auf den Wert gerundet, der eine gerade Zahl in der niedrigstwertigen Stellenposition hat (dies wird als "Banker-Rundung" bezeichnet). Ein Null-Ergebnis hat immer ein Vorzeichen von 0 und eine Skala von 0.

Es ist leicht zu sehen, dass sie ToString(string) in diesem Absatz nicht berücksichtigt haben, aber ich bin geneigt zu denken, dass es in diese Beschreibung passt.

+2

Es ist möglich, dass Sie berücksichtigen sollten, dass C# keine 'ToString (string)' Methode hat. Das .NET Framework funktioniert. Ich bin mir nicht sicher, ob das .NET Framework den Regeln einer bestimmten Programmiersprache unterliegt. –

Antwort

1

ToString() standardmäßig Formate nach der Culture, nicht nach einem rechnerischen Aspekt der Spezifikation. Offenbar erwartet die Culture für Ihr Gebietsschema (und die meisten, wie es aussieht) Rundung von Null.

Wenn Sie ein anderes Verhalten möchten, können Sie eine IFormatProvider um ToString()

Ich dachte, die oben passieren, aber Sie sind richtig, dass es immer von Null egal rundet weg die Culture. Überprüfen Sie die Links in den Kommentaren auf Beweise.

+0

Ich dachte das auch kürzlich, aber nachdem ich dazu aufgefordert wurde, konnte ich nirgends in CultureInfo oder NumberFormatInfo finden wo die Rundung spezifiziert oder bestimmt wurde. Kannst du darauf hinweisen, wo das eigentlich passiert? –

+0

kann ich nicht, und es sieht so aus, als ob ich wahrscheinlich falsch liege. * Lizenz Warnung: CLR Quelle nach Link * Es sieht aus wie http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx?s=NumberToStringFormat#L1457 NumberToStringFormat Aufrufe http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx ? s = NumberToStringFormat # L838 RoundNumber, das immer von Null wegrundet. –

+1

Da die beste Antwort scheint, dass es eine Inkonsistenz in der Sprache ist, werde ich diese Antwort als die Antwort markieren. Vielen Dank! – stack

1

Höchstwahrscheinlich, weil dies die Standardmethode für den Umgang mit Währungen ist. Der Anstoß für die Schaffung von Dezimalzahlen war, dass Gleitkommawerte mit Währungswerten nur schlecht umgehen können. Sie würden also erwarten, dass die Regeln besser mit den Rechnungslegungsstandards übereinstimmen als mathematische Korrektheit.

+0

Ich bin mir nicht sicher, ob ich deine Antwort verstehe, da sie sich auf die Frage bezieht. – stack

6

Wenn Sie die Spezifikation sorgfältig lesen, werden Sie feststellen, dass hier keine Inkonsistenz besteht.

Hier ist, dass Absatz wieder mit den wichtigen Teilen hervorgehoben:

Das Ergebnis eines Betrieb auf Werte vom Typ dezimal ist das, was aus der Berechnung ein exaktes Ergebnisses (Erhaltung Skala führen würde, wie sie für jeder Operator) und dann runden, um die Darstellung anzupassen. Die Ergebnisse werden auf nächstgelegenen darstellbaren Wert gerundet und, wenn ein Ergebnis gleich zwei darstellbaren Werten entspricht, auf den Wert gerundet, der eine gerade Zahl in der niedrigstwertigen Stellenposition hat (dies wird als "Banker-Rundung" bezeichnet). Ein Null-Ergebnis hat immer ein Zeichen von 0 und eine Skala von 0.

Dieser Teil der Spezifikation gilt für arithmetische Operationen auf decimal; Die Formatierung von Strings gehört nicht dazu, und selbst wenn dies der Fall wäre, wäre das egal, weil Ihre Beispiele eine niedrige Genauigkeit haben.

das Verhalten in der Spezifikation genannt zu demonstrieren, den folgenden Code verwenden:

Decimal d1 = 0.00000000000000000000000000090m; 
Decimal d2 = 0.00000000000000000000000000110m; 

// Prints: 0.0000000000000000000000000004 (rounds down) 
Console.WriteLine(d1/2); 

// Prints: 0.0000000000000000000000000006 (rounds up) 
Console.WriteLine(d2/2); 

Das ist das alles spec redet. Wenn das Ergebnis einer Berechnung die Genauigkeitsgrenze des Typs decimal (29 Ziffern) überschreiten würde, wird die Rundung des Bankers verwendet, um zu bestimmen, wie das Ergebnis aussehen wird.

+0

"Es ist leicht zu sehen, dass sie ToString (string) in diesem Absatz nicht berücksichtigt haben, aber ich bin geneigt zu denken, dass es in diese Beschreibung passt." – stack