Dlaczego .NET używa domyślnie zaokrąglania bankiera?

271

Zgodnie z dokumentacją decimal.Roundmetoda wykorzystuje algorytm zaokrąglania do parzystości, który nie jest powszechny w większości aplikacji. Dlatego zawsze kończę na napisaniu niestandardowej funkcji, aby wykonać bardziej naturalny algorytm zaokrąglania do połowy:

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

Czy ktoś zna przyczynę tej decyzji dotyczącej projektu ramowego?

Czy jest jakaś wbudowana implementacja algorytmu zaokrąglania do połowy w strukturze? A może jakiś niezarządzany interfejs Windows API?

Może to być mylące dla początkujących, którzy po prostu piszą, decimal.Round(2.5m, 0)oczekując 3, ale zamiast tego otrzymują 2.

Darin Dimitrov
źródło
105
Zaokrąglanie w górę nie jest „bardziej naturalne”. Natura nie ma z tym nic wspólnego. Tego po prostu nauczyłeś się w szkole, kiedy nauczyłeś się pojęcia „zaokrąglania”. Lekcje w szkole podstawowej nie zawsze malują pełny obraz.
Rob Kennedy,
45
@Rob I dlatego jest bardziej naturalny , nawet jeśli nie jest poprawny
Pacerier
10
Nie rozumiem @Pacerier. Wyjaśniłem, dlaczego to nie jest naturalne, a ty mówisz, że tak właśnie jest . Jak mój argument działa wbrew mojemu wnioskowi, który jest przeciwieństwem twojego? Rzeczy, do których przywykłeś, mogą wydawać się naturalne, a czasami w przenośni mówimy, że coś jest „drugą naturą”, ale to nie czyni ich naturalnymi.
Rob Kennedy
16
@Rob Mówię, że to naturalne, ponieważ wydaje się naturalne. Czy wiesz, że istnieje 36 różnych obiektów o tej samej nazwie zmiennej naturalnej, prawda?
Pacerier
9
z natury analogia, więc użycie niewłaściwego słowa; ale to pedantyczne. Może „zwykłe” byłoby lepszym słowem w użyciu. „Jakie jest zwykłe zaokrąglanie, które ludzie robią”> 0,5 idzie do 1,0
co to jest

Odpowiedzi:

196

Prawdopodobnie dlatego, że jest to lepszy algorytm. W trakcie wielu zaokrągleń uśredniasz, że wszystkie .5 kończą zaokrąglanie równo w górę i w dół. To daje lepsze oszacowania rzeczywistych wyników, jeśli na przykład dodajesz kilka zaokrąglonych liczb. Powiedziałbym, że chociaż nie jest to, czego niektórzy mogą się spodziewać, to prawdopodobnie bardziej właściwa rzecz do zrobienia.

Kibee
źródło
71
zakładając, że masz płaski rozkład nieparzystych i parzystych danych wejściowych
jk.
7
+1 za lepszy algorytm , chociaż Ostemar ma właściwą odpowiedź ( stackoverflow.com/questions/311696/... )
Ian Boyd
2
@Ian, daję również tę odpowiedź +1. W każdym razie możemy przenieść „zaakceptowaną odpowiedź” Może OP może to zrobić. Rzeczywista odpowiedź na pytanie „dlaczego” korzysta z tej metody, znajduje się w połowie strony. Chociaż bardzo podoba mi się zwiększenie liczby powtórzeń, które otrzymuję raz w tygodniu z tej odpowiedzi.
Kibbee
@Kibbee - dziękuję za zwiększenie mojej odpowiedzi. Myślę, że OP musi zmienić zaakceptowaną odpowiedź, jeśli uzna to za stosowne?
Ostemar,
-1 do stwierdzenia, że ​​jest to lepszy algorytm. - Biorąc pod uwagę losową próbkę liczby wykorzystującą zaokrąglanie bankiera, ostatecznie uzyskasz więcej liczb na pozycjach parzystych niż nieparzystych. - Dopiero po uśrednieniu tych liczb ponownie otrzymasz podobny spread do pierwotnego rozkładu. - Jednak jeśli na przykład spiszesz te dane na wykresie rozrzutu, możesz zobaczyć sztuczne grupowanie.
paul23
437

Inne odpowiedzi z powodów, dla których algorytm bankiera (czyli półokrągły do ​​parzystego ) jest dobrym wyborem, są całkiem poprawne. Nie ma negatywnego lub pozytywnego nastawienia tak bardzo, jak okrągła połowa odległości od metody zerowej w najbardziej rozsądnych rozkładach.

Pytanie brzmiało jednak, dlaczego .NET domyślnie korzysta z faktycznego zaokrąglania przez Banker - a odpowiedź brzmi: Microsoft przestrzegał standardu IEEE 754 . Jest to również wspomniane w MSDN dla Math.Round pod uwagami .

Należy również pamiętać, że .NET obsługuje alternatywną metodę określoną przez IEEE, podając MidpointRoundingwyliczenie. Mogliby oczywiście zapewnić więcej alternatyw dla rozwiązywania więzi, ale wybrali po prostu spełnienie standardu IEEE.

Ostemar
źródło
17
Dlaczego więc IEEE 754 śledzi bankowców? Ta (wciąż dobra) odpowiedź po prostu mija wiadro.
Henk Holterman
4
@HenkHolterman Prawdopodobnie z powodu tego, co wspomniano w innych odpowiedziach (i jak podsumowałem); nie cierpi (zbyt mocno) z powodu negatywnego lub pozytywnego uprzedzenia i jako taki powoduje bardziej rezonansowe ustawienie domyślne dla większości dystrybucji i domen problemowych.
Ostemar,
Myślę, że to interesujące, ponieważ IEEE 754 jest standardem dla liczb zmiennoprzecinkowych, których nie ma w systemie dziesiętnym. Być może wynika to z IEEE 754, aby ten sam algorytm był używany do zaokrąglania liczby podwójnej jako dziesiętnej.
Brandon Barkley,
1
@BrandonBarkley Dziesiętny lub dziesiętny jest liczbą zmiennoprzecinkową, a IEEE 754 zawiera dziesiętne liczby zmiennoprzecinkowe.
Pablo H
88

Chociaż nie mogę odpowiedzieć na pytanie „Dlaczego projektanci Microsoftu wybrali tę opcję jako domyślną?”, Chcę jedynie zaznaczyć, że dodatkowa funkcja nie jest potrzebna.

Math.Roundpozwala określić MidpointRounding:

  • ToEven - Gdy liczba znajduje się w połowie drogi między dwoma innymi, jest zaokrąglana w kierunku najbliższej liczby parzystej.
  • AwayFromZero - Gdy liczba znajduje się w połowie drogi między dwoma innymi, jest zaokrąglana w kierunku najbliższej liczby, która jest od zera.
Michael Stum
źródło
8
I jak wspomniałem w powiązanych wątkach, upewnij się, że jesteś konsekwentny w zaokrąglaniu - jeśli czasami robisz zaokrąglanie w bazie danych, a czasem w .net, będziesz miał dziwne, jeden cent błędy, które zajmie Ci tygodnie rozwiązać.
Chris
59
Pewnego razu klient zapłacił mi ponad 40 000 $ za wyśledzenie błędu zaokrąglenia 0,11 $ między dwiema liczbami, które były po prostu nieśmiałe od 1 MILIONA dolarów; 0,11 $ było spowodowane różnicą w 8-cyfrowym błędzie zaokrąglenia między komputerem mainframe a SQL Server. Porozmawiaj o perfekcjonistce!
EJ Brennan
12
@EJB - prawdopodobnie byłbym perfekcjonistą, gdybym miał do czynienia z miliardem dolarów ;-)
royse41,
12
@EJ Brennan: Zajęło ci 40 tys. Dolarów, żeby to zrozumieć? Cały czas widzę takie problemy, zaokrąglanie jest przyczyną nr 1, normalizacja podwójna / zmiennoprzecinkowa jest przyczyną nr 2, błąd programisty nr 3–3 można natychmiast ustawić na wartość 1, jeśli nie ma predefiniowanych przypadków testowych. btw, proszę, proszę o kontakt ze swoim klientem miliardowym, myślę, że mógłbym znaleźć jeszcze kilka błędów w wysokości 40 000 $ w jego systemie! : D
3
@seanxe: Lub jeśli widziałeś Office Space. Poważnie, ilekroć zobaczysz tajemnicze, drobne niedokładności w pieniądzach, prawie zawsze dobrym pomysłem jest rozwiązanie zagadki dokładnie tak, jak się dzieją. Możliwe, że zdecydujesz się nie naprawiać błędów, ale wiedza o przyczynie nadal ma wartość. Założę się, że wielu ludzi, którzy pracują z pieniędzmi, z przyjemnością zauważają nawet małe nieścisłości.
Brian
22

Dziesiętne są najczęściej używane do pieniędzy ; zaokrąglanie przez bankiera jest powszechne podczas pracy z pieniędzmi . Lub możesz powiedzieć.

Głównie bankierzy potrzebują typu dziesiętnego; dlatego też „zaokrągla bankiera”

Zaokrąglanie przez bankierów ma tę zaletę, że średnio ten sam wynik uzyskasz, jeśli:

  • zaokrąglić zestaw „wierszy faktury” przed ich dodaniem,
  • lub dodaj je, a następnie zaokrąglij sumę

Zaokrąglanie przed zsumowaniem pozwoliło zaoszczędzić sporo pracy w dniach poprzedzających komputery.

(W Wielkiej Brytanii, kiedy szliśmy po przecinku, banki nie radziły sobie z pół pensami, ale przez wiele lat wciąż było pół pensów, a sklep często miał ceny kończące się na pół pensów - więc zaokrąglanie)

Ian Ringrose
źródło
8
„Dziesiętne są głównie używane do pieniędzy” ... i wszystko inne, co nie jest liczbą całkowitą.
John Tyree
3
@JohnTyree, Nieprawda przez większość czasu, gdy liczba podwójna / zmiennoprzecinkowa jest używana, gdy nie jest liczbą całkowitą. patrz stackoverflow.com/questions/2545567/…
Ian Ringrose,
3
Łał. Śmieszny błąd z mojej strony. Decmials tak Dziesiętne, nie. Dla potomnych zgadzam się z oryginalnym sentymentem tutaj.
John Tyree,
Bankowcy mogą lubić zaokrąglanie banków, ale księgowi mogą nie być ich fanami, mówią, że różnica 0,005 powinna zaokrąglać w górę o 0,01, niezależnie od tego, czy jest to liczba nieparzysta, czy parzysta.
JustAMartin,
0

Użyj innego przeciążenia funkcji Round:

decimal.Round(2.5m, 0,MidpointRounding.AwayFromZero)

Wyjdzie 3 . A jeśli użyjesz

decimal.Round(2.5m, 0,MidpointRounding.ToEven)

dostaniesz zaokrąglenie bankiera.

Omid Sadeghi
źródło
4
To nie odpowiada na pytanie, dlaczego Zaokrąglanie bankiera zostało wybrane jako domyślne.
shortstuffsushi