2010-02-12 12 views
18

Vedo che, in C#, l'arrotondamento a decimal, per impostazione predefinita, utilizza MidpointRounding.ToEven. Questo è previsto ed è ciò che detta la specifica C#. Tuttavia, data la seguente:Perché .NET decimal.ToString (stringa) è più o meno lontano da zero, apparentemente incoerente con le specifiche della lingua?

  • Un decimal dVal
  • Un formato string sFmt che, quando passato a dVal.ToString(sFmt), si tradurrà in una stringa contenente una versione arrotondata della dVal

... è apparente che decimal.ToString(string) restituisce un valore arrotondato utilizzando MidpointRounding.AwayFromZero. Ciò sembrerebbe essere una contraddizione diretta della specifica C#.

La mia domanda è questa: c'è una buona ragione questo è il caso? O è solo un'incongruenza nella lingua?

Di seguito, per riferimento, ho incluso del codice che scrive per consolare un assortimento di risultati dell'operazione di arrotondamento e risultati dell'operazione decimal.ToString(string), ciascuno su ogni valore in una matrice di valori decimal. Le uscite effettive sono incorporate. Dopodiché, ho incluso un paragrafo pertinente dalla sezione C# Language Specification sul tipo decimal.

Il codice di esempio:

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(); 
} 


Il paragrafo dalla sezione 4.1.7 del linguaggio C# Specification ("Il tipo di decimali") (ottenere il pieno spec here (.doc)):

Il risultato di un'operazione sui valori di tipo decimale è quello che risulterebbe dal calcolo di un risultato esatto (scala di conservazione, come definito per ciascun operatore) e quindi arrotondamento per adattarsi alla rappresentazione. I risultati sono arrotondati al valore rappresentativo più vicino e, quando un risultato è ugualmente vicino a due valori rappresentabili, al valore che ha un numero pari nella posizione di cifra meno significativa (questo è noto come "arrotondamento bancario"). Un risultato pari a zero ha sempre un segno di 0 e una scala di 0.

È facile vedere che in questo paragrafo potrebbero non aver considerato ToString(string), ma sono propenso a pensare che si adatti a questa descrizione.

+2

È possibile che si consideri che C# non ha il metodo 'ToString (stringa)'. .NET Framework lo fa. Non sono sicuro che .NET Framework sia vincolato all'osservanza delle regole di un particolare linguaggio di programmazione. –

risposta

1

ToString() per i formati di default in base allo Culture, non secondo un aspetto computazionale della specifica. Apparentemente lo Culture per la propria localizzazione (e la maggior parte, a giudicare dall'aspetto) si aspetta un arrotondamento da zero.

Se si vuole un comportamento diverso, si può passare un IFormatProvider per ToString()

ho pensato sopra, ma lei ha ragione che arrotonda sempre lontano da zero, non importa il Culture. Controlla i link nei commenti per prova.

+0

L'ho pensato anche io di recente, ma dopo essere stato richiamato non sono riuscito a trovare alcunché in CultureInfo o NumberFormatInfo dove l'arrotondamento è specificato o determinato. Puoi indicare dove si verifica effettivamente? –

+0

Non posso, e sembra che io sia probabilmente sbagliato. * Avviso di licenza: sorgente CLR dopo il collegamento * Sembra http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx?s=NumberToStringFormat#L1457 NumberToStringFormat chiama http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx ? s = NumberToStringFormat # L838 RoundNumber che si arrotonda sempre da zero. –

+1

Dal momento che la migliore risposta sembra essere che è un'incongruenza nella lingua, contrassegnerò questa risposta come risposta. Grazie! – stack

1

Molto probabilmente perché questo è il modo standard di trattare la valuta. L'impeto per la creazione di decimali è stato che il punto in virgola mobile fa un pessimo lavoro di gestione dei valori di valuta, quindi ti aspetteresti che le regole siano più allineate con gli standard contabili rispetto alla correttezza matematica.

+0

Non sono sicuro di aver compreso la risposta in quanto riguarda la domanda. – stack

6

Se leggi attentamente le specifiche, vedrai che non c'è alcuna incoerenza qui.

Ecco quel paragrafo nuovamente, con le parti importanti evidenziati:

Il risultato di un operazione su valori di tipo decimali è quello che risulterebbe da calcolare un risultato esatto (preservando scala, come definito per ogni operatore) e quindi arrotondando per adattarsi alla rappresentazione. I risultati sono arrotondati al valore rappresentativo più vicino e, quando un risultato è ugualmente vicino a due valori rappresentabili, al valore che ha un numero pari nella posizione di cifra meno significativa (questo è noto come "arrotondamento bancario"). Un risultato zero ha sempre un segno di 0 e una scala di 0.

Questa parte della specifica applica alle operazioni aritmetiche sudecimal; la formattazione delle stringhe non è una di quelle, e anche se lo fosse, non avrebbe importanza perché i tuoi esempi sono a bassa precisione.

per dimostrare il comportamento di cui nelle specifiche, utilizzare il seguente codice:

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); 

Questo è tutto la specifica sta parlando. Se il risultato di alcuni calcoli supera il limite di precisione del tipo decimal (29 cifre), viene utilizzato l'arrotondamento del banco per determinare quale sarà il risultato.

+0

"È facile vedere che potrebbero non aver considerato ToString (stringa) in questo paragrafo, ma sono incline a pensare che si adatti a questa descrizione." – stack

Problemi correlati