Чому .NET використовує округлення банкіра за замовчуванням?


271

Згідно з документацією, decimal.Roundметод використовує алгоритм кругового до рівного, який не є загальним для більшості застосунків. Тому я завжди закінчую писати власну функцію, щоб зробити більш природний алгоритм повороту:

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

Хтось знає причину цього рішення щодо дизайну?

Чи є вбудована реалізація алгоритму круглої половини в рамках? А може, якийсь некерований API Windows?

Це може ввести в оману для початківців, які просто пишуть, decimal.Round(2.5m, 0)очікуючи 3 в результаті, але отримуючи 2 натомість.


105
Закруглення не є "більш природним". Природа тут ні до чого. Це просто те, що ви дізналися в класах, коли ви засвоїли поняття "округлення". Уроки на уроках не завжди малюють повну картину.
Роб Кеннеді

45
@Rob І тому це більш природно , хоча це не правильно
Pacerier

10
Я не розумію, @Pacerier. Я пояснив , чому це НЕ природно, а ви говорите , що це насправді , чому це є природним. Як мій аргумент працює проти мого висновку, протилежного вашому? Речі, до яких ви звикли, можуть відчувати себе природними, а іноді ми образно говоримо, що щось є "другою природою", але це не робить їх природними.
Роб Кеннеді

16
@Rob Я кажу, що це природно, тому що він відчуває себе природним. Ви знаєте, що існує 36 різних об'єктів із однаковою природною назвою змінної ?
Pacerier

9
натур, безумовно, аналог, тому це неправильне слово; але це педантично. Можливо, "звичайним" було б краще слово використовувати .. "що таке звичайне округлення, яке роблять люди"> 0,5 йде до 1,0
чомузвід

Відповіді:


196

Можливо, тому, що це кращий алгоритм. Протягом багатьох проведених округлень ви оціните, що всі .5 в кінці округлиться однаково вгору і вниз. Це дає кращі оцінки фактичних результатів, якщо ви, наприклад, додаєте купу округлих чисел. Я б сказав, що хоча це не те, чого деякі можуть очікувати, це, мабуть, правильніше зробити.


71
якщо припустимо, ви маєте рівний розподіл непарних і парних входів звичайно
jk.

7
+1 для кращого алгоритму , хоча Ostemar має фактичний відповідь ( stackoverflow.com/questions/311696 / ... )
Ian Boyd

2
@Ian, я також даю цю відповідь +1. У будь-якому випадку ми можемо перенести "прийняту відповідь", можливо, ОП може це зробити. Фактична відповідь на те, чому він використовує цей метод, - це половина сторінки. Хоча мені дуже подобається посилення реп, я отримую приблизно раз на тиждень з цієї відповіді.
Kibbee

@Kibbee - дякую за підштовхування моєї відповіді. Я думаю, що до ОП потрібно змінити прийняту відповідь так, як він вважає за потрібне?
Остемар

-1 для твердження, що це кращий алгоритм. - З огляду на випадкову вибірку числа за допомогою округлення банкіра, ви отримаєте більше чисел на парних позиціях, ніж непарних позицій. - Лише після того, як ви середньо оціните ці цифри, ви знову отримаєте схожий спред на початковий розподіл. - Однак, якщо ви, наприклад, розмістили б ці дані в діаграмі розкидання, можна побачити штучне групування.
paul23

437

Інші відповіді з причин, чому алгоритм Банкіра (він же кругла половина до парного ) є правильним вибором, є цілком правильним. Він не страждає від негативного чи позитивного зміщення так само, як кругла половина від нульового методу у більшості розумних розподілів.

Але питання полягало в тому, чому .NET використовує фактичне округлення Banker за замовчуванням - і відповідь полягає в тому, що Microsoft дотримується стандарту IEEE 754 . Про це також згадується в MSDN для Math.Round під зауваженнями.

Також зауважте, що .NET підтримує альтернативний метод, визначений IEEE шляхом надання MidpointRoundingперерахунку. Звичайно, вони могли запропонувати більше альтернатив для вирішення зв'язків, але вони вирішили просто виконати стандарт IEEE.


17
Отже, чому IEEE 754 слідкує за округленням банкірів? Ця (ще гарна) відповідь просто передає відро.
Хенк Холтерман

4
@HenkHolterman Можливо, через те, що згадується в інших відповідях (і як я підсумував); він не страждає (занадто сильно) від негативного чи позитивного упередження і, як такий, створює більш резонансний дефолт для більшості дистрибутивів та проблемних доменів.
Остемар


Я думаю, що це цікаво, тому що IEEE 754 є еталоном для чисел з плаваючою комою, десяткового числа - ні. Можливо, з цього випливає IEEE 754, щоб той самий алгоритм був використаний для округлення Double, ніж Decimal.
Брендон Барклі

1
@BrandonBarkley Десятковий або десятковий - це число з плаваючою комою, а IEEE 754 включає десяткові числа з плаваючою комою.
Пабло Н

88

Хоча я не можу відповісти на питання "Чому дизайнери Microsoft вибрали це за замовчуванням?", Я просто хочу зазначити, що зайва функція не потрібна.

Math.Roundдозволяє вказати MidpointRounding:

  • ToEven - Коли число знаходиться на півдорозі між двома іншими, воно округляється до найближчого парного числа.
  • AwayFromZero - Коли число знаходиться на півдорозі між двома іншими, воно округляється до найближчого числа, яке знаходиться далеко від нуля.

8
І як я вже згадував у споріднених потоках, переконайтеся, що ви послідовні в своєму округленні - якщо ви іноді робити округлення в базі даних, а іноді в .net, у вас з’являться дивні помилки на один цент, які займуть вас тижнів здогадатися.
chris

59
Клієнт одного разу заплатив мені понад 40 000 доларів США, щоб виявити помилку округлення 0,11 долара між двома номерами, які були просто сором’язливими 1 мільярд доларів; $ 0,11 було пов'язано з різницею помилки округлення восьмизначних між основним кадром та SQL Server. Говоріть про перфекціоніста!
EJ Brennan

12
@EJB - я, мабуть, був би перфекціоністом, якби мав справу з мільярдом доларів ;-)
royse41

12
@EJ Brennan: На це вам знадобилося $ 40 000? Я бачу такі проблеми, як це весь час, округлення є причиною №1, подвійне / поплавкове нормалізація є причиною №2, помилка програміста №3 - №3 можна негайно встановити на №1, якщо немає попередньо визначених тестових випадків. До речі, ви можете, будь ласка, зв’язати мене з вашим клієнтом-мільярдером, я думаю, я міг би знайти ще кілька помилок на $ 40 000 у його системі! : D

3
@seanxe: Або якщо ви бачили Office Space. Серйозно, щоразу, коли ви бачите загадкові, крихітні неточності у грошах, розгадування загадки про те, як саме вони відбуваються, майже завжди є хорошою ідеєю. Можливо, ви вирішите не виправляти помилки, але знаючи, що є основною причиною, все ще має значення. Надіюсь, багато людей, які працюють з грошима, із задоволенням помічають навіть крихітні неточності.
Брайан

22

Десяткові знаки в основному використовуються для грошей ; Заокруглення банкіра поширене при роботі з грошима . Або ви могли сказати.

В основному банкіри потребують десяткового типу; тому це "банкірське округлення"

Банківські заокруглення мають ту перевагу, що в середньому ви отримаєте такий же результат, якщо:

  • округляйте набір "рядків рахунків-фактур", перш ніж додавати їх,
  • або додати їх, а потім округлити загальну суму

Округлення перед додаванням врятувало багато роботи за дні перед комп'ютерами.

(У Великій Британії, коли ми їздили десятковими банками, банки не мали справу з половиною пенсів, але багато років все ще була монета на пів пенси, а в магазинах часто ціни закінчувались на пів пенси - так багато округлення)


8
"Десяткові знаки в основному використовуються для грошей" ... і все інше, що не є цілим числом.
Джон Тайрі

3
@JohnTyree, Неправда, більшість часу використовується подвійний / float, коли це не ціле число. см stackoverflow.com/questions/2545567 / ...
Ian Рінгроуза

3
Ого. Смішна помилка з мого боку. Decmials, так. Десяткові знаки, немає. Що стосується нащадків, я згоден з оригінальними почуттями тут.
Джон Тайрі

Банкірам, можливо, подобається округлення банкірів, але бухгалтери можуть не бути його шанувальниками, вони кажуть, що різниця 0,005 повинна привести до округлення на 0,01, не залежно від того, непарна чи парна кількість.
JustAMartin

0

Використовуйте ще одне перевантаження функції Round так:

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

Він виведе 3 . А якщо використовувати

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

ви отримаєте заокруглення банкіра.


4
Це не дає відповіді на питання, чому «Заокруглення Банкіра» було обрано за замовчуванням.
shortstuffsushi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.