Perché .NET utilizza l'arrotondamento del banco come predefinito?
Domanda
In base alla documentazione, decimal.Round
utilizza un algoritmo round-to-even che non è comune per la maggior parte delle applicazioni. Quindi finisco sempre per scrivere una funzione personalizzata per eseguire l'algoritmo round-half-up più naturale:
public static decimal RoundHalfUp(this decimal d, int decimals)
{
if (decimals < 0)
{
throw new ArgumentException("The decimals must be non-negative",
"decimals");
}
decimal multiplier = (decimal)Math.Pow(10, decimals);
decimal number = d * multiplier;
if (decimal.Truncate(number) < number)
{
number += 0.5m;
}
return decimal.Round(number) / multiplier;
}
Qualcuno conosce il motivo dietro questa decisione di progettazione del framework?
Esiste una implementazione integrata dell'algoritmo round-half-up nel framework? O forse qualche API Windows non gestita?
Potrebbe essere fuorviante per i principianti che scrivono semplicemente decimal.Round (2.5m, 0)
aspettandosi 3 come risultato ma ottenendo invece 2.
Soluzione
Probabilmente perché è un algoritmo migliore. Nel corso di molti arrotondamenti effettuati, calcolerai la media del fatto che tutti gli 0,5 vanno a finire ugualmente su e giù. Ciò fornisce stime migliori dei risultati effettivi, ad esempio, aggiungendo un gruppo di numeri arrotondati. Direi che anche se non è quello che alcuni potrebbero aspettarsi, è probabilmente la cosa più corretta da fare.
Altri suggerimenti
L'altro risponde con i motivi per cui l'algoritmo del banchiere (ovvero arrotondare la metà a pari ) è una buona scelta sono abbastanza corretti. Non soffre di parzialità negativa o positiva tanto quanto il metodo arrotondato a metà dello zero sulle distribuzioni più ragionevoli.
Ma la domanda era perché .NET utilizza l'arrotondamento effettivo di Banker come predefinito - e la risposta è che Microsoft ha seguito la IEEE 754 standard. Questo è anche menzionato in MSDN for Math.Round sotto Note.
Si noti inoltre che .NET supporta il metodo alternativo specificato da IEEE fornendo l'enumerazione MidpointRounding
. Naturalmente avrebbero potuto fornire più alternative per risolvere i legami, ma hanno scelto di soddisfare lo standard IEEE.
Anche se non posso rispondere alla domanda di " Perché i designer di Microsoft hanno scelto questo come predefinito? " ;, voglio solo sottolineare che una funzione aggiuntiva non è necessaria.
Math.Round
consente di specificare un MidpointRounding
:
- ToEven: quando un numero si trova a metà strada tra altri due, viene arrotondato verso il numero pari più vicino.
- AwayFromZero - Quando un numero si trova a metà strada tra altri due, viene arrotondato verso il numero più vicino che è lontano da zero.
I decimali vengono utilizzati principalmente per denaro ; L'arrotondamento del banchiere è comune quando si lavora con denaro . O potresti dire.
Sono soprattutto i banchieri che hanno bisogno del tipo decimale; quindi lo fa & # 8220; arrotondamento del banchiere & # 8221;
L'arrotondamento dei banchieri ha il vantaggio che in media otterrai lo stesso risultato se:
- arrotondare una serie di & # 8220; linee di fattura & # 8221; prima di aggiungerli,
- o aggiungili, quindi arrotonda il totale
L'arrotondamento prima di sommare ha risparmiato molto lavoro nei giorni precedenti i computer.
(Nel Regno Unito quando siamo andati le banche decimali non avrebbero gestito la metà di pence, ma per molti anni c'era ancora una moneta di metà pence e il negozio aveva spesso prezzi che terminavano in metà pence & # 8211; così tanti arrotondamenti)
Usa un altro sovraccarico della funzione Round in questo modo:
decimal.Round(2.5m, 0,MidpointRounding.AwayFromZero)
Verrà visualizzato 3 . E se usi
decimal.Round(2.5m, 0,MidpointRounding.ToEven)
otterrai l'arrotondamento del banchiere.
Inoltre, tieni presente che " arrotondamento " tramite una stringa di formato (come " 0 ") produce un risultato diverso da " Math.Round () " ;. Vale a dire, quel 5, .5, ecc. È sempre arrotondato per eccesso:
let d, d' = 2.5, 3.5
Debug.WriteLine(Math.Round(d)) // 2.5 -> 2
Debug.WriteLine(d.ToString("0")) // 2.5 -> 3
Debug.WriteLine(Math.Round(d')) // 3.5 -> 4
Debug.WriteLine(d'.ToString("0")) // 3.5 -> 4
let dd, dd' = 2.25, 2.35
Debug.WriteLine(Math.Round(dd, 1)) // 2.25 -> 2.2
Debug.WriteLine(dd.ToString("0.0")) // 2.25 -> 2.3
Debug.WriteLine(Math.Round(dd', 1)) // 2.35 -> 2.4
Debug.WriteLine(dd'.ToString("0.0")) // 2.35 -> 2.4