Почему .NET по умолчанию использует банковское округление?

Согласно документации, метод 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;
}

Кто-нибудь знает причину этого решения по дизайну фреймворка?

Есть ли в фреймворке встроенная реализация алгоритма округления до половины? Или, может быть, какой-то неуправляемый Windows API?

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


person Darin Dimitrov    schedule 22.11.2008    source источник
comment
›Я ожидал 72,5, но результат 72,4 - нет. Вы хотите округлить 2,444444444444444449 до трех? (Если вы округляли до нуля десятичных знаков)   -  person Will Dean    schedule 22.11.2008
comment
Округление не более естественно. Природа тут ни при чем. Это просто то, что вы узнали в начальной школе, когда выучили понятие округления. Уроки в начальной школе не всегда рисуют полную картину.   -  person Rob Kennedy    schedule 22.11.2008
comment
@Rob И поэтому это более естественно, хотя и не правильно   -  person Pacerier    schedule 18.10.2011
comment
Я не понимаю, @Pacerier. Я объяснил, почему это не естественно, и вы говорите, что на самом деле это естественно. Как мой аргумент работает против моего вывода, противоположного вашему? Вещи, к которым вы привыкли, могут казаться естественными, и иногда мы образно говорим, что что-то является второй натурой, но это не делает их естественными.   -  person Rob Kennedy    schedule 18.10.2011
comment
@ Роб Я говорю, что это естественно, потому что кажется естественным. Вы ведь знаете, что существует 36 разных объектов с одинаковым именем переменной natural, верно?   -  person Pacerier    schedule 18.10.2011
comment
точный аналог природы, поэтому использовать это слово неуместно; но это педантично. Может быть, лучше было бы использовать слово «обычный» ... как обычно округляются люди? ›0,5 переходит в 1,0   -  person whytheq    schedule 25.05.2012
comment
Кстати, интересно отметить, что при округлении двойника при преобразовании в строку используется округление AwayFromZero (т.е. 0.5.ToString("0") == "1"). Я думаю, что обычно при округлении вы делаете это для отображения значения на экране. Так что это делает его действительно интересным.   -  person Matthijs Wessels    schedule 05.09.2012
comment
@MatthijsWessels Да, я тоже нахожу это очень странным. Это общая несогласованность, которая присутствует с самого начала .NET. Вот дополнительный пример для тех, кто этого не знает: если d = 9.825m - это Decimal (точно представляемый точно), то такие вещи, как decimal.Round(d, 2) и Math.Round(d, 2), дают 9.82, а форматирование, такое как d.ToString("F2"), d.ToString("N2") и d.ToString("0.00"), дает "9.83" (десятичный разделитель в зависимости от языка и региональных параметров текущего потока). Не дружелюбный.   -  person Jeppe Stig Nielsen    schedule 27.09.2012
comment
@JeppeStigNielsen Единственная причина, которую я могу придумать, заключается в том, что при округлении значения для отображения на экране вам не нужно статистическое преимущество округления банкиров. Более естественное ощущение от нулевого округления может быть лучшим вариантом. Следовательно, преобразование в строку происходит примерно так. При округлении значений, которые должны использоваться в других расчетах, полезны статистические преимущества округления банкиров. Именно в такой ситуации вы бы использовали Math.Round.   -  person Matthijs Wessels    schedule 27.09.2012
comment
Оффтоп: Округление банкиров должно быть беспристрастным, что теоретически и является. Но на практике четных чисел (в финансовом мире) гораздо больше, что делает этот вопрос спорным.   -  person Jowen    schedule 29.05.2013
comment
@Jowen: Это правда, что многие цены заканчиваются девятками, но это не вызывает особого предубеждения. Например, цена может быть с налогом (налогами) или без них и т.п. И, конечно же, большие цены часто не доводятся до последних цифр 9 (до последних 5). В общем, во всех финансовых приложениях, в которых я участвовал до сих пор, округление банкиров отлично работает.   -  person Luaan    schedule 13.02.2014
comment
@Pacerier: В большинстве случаев в банковских приложениях вы просто выполняете огромное количество суммирований. Каждое суммирование может потенциально добавить к результату ошибку. Чтобы свести к минимуму ошибку, используемый алгоритм не должен быть предвзятым, что означает, что иногда следует округлять в большую, а иногда в меньшую сторону. На самом деле не имеет значения, что заставляет ваш мозг чувствовать естественность. Что действительно важно, так это необходимость, обусловленная конкретным вариантом использования или требованием. Если раунд банкиров, возможно, является лучшим решением проблемы ... ну ... его естественно использовать!   -  person Richard Gomes    schedule 12.02.2018
comment
@Pacerier :: Между прочим, финансовые приложения (я имею в виду написанные высококвалифицированными разработчиками) используют арифметику с фиксированной точкой, а не с плавающей точкой. При использовании арифметики с фиксированной точкой все вычисления точны и отсутствуют ошибки округления.   -  person Richard Gomes    schedule 12.02.2018


Ответы (5)


Наверное, потому что это лучший алгоритм. В течение многих выполненных округлений вы усредните, что все 0,5 округляются одинаково в большую и меньшую сторону. Это дает лучшую оценку фактических результатов, если вы, например, добавляете кучу округленных чисел. Я бы сказал, что даже если это не то, чего некоторые могут ожидать, это, вероятно, более правильный поступок.

person Kibbee    schedule 22.11.2008
comment
при условии, что у вас есть плоское распределение нечетных и четных входов, конечно - person jk.; 22.01.2010
comment
+1 для лучшего алгоритма, хотя у Остемара есть фактический ответ (stackoverflow.com/questions/311696/) - person Ian Boyd; 09.09.2011
comment
@ Ян, я тоже даю +1. В любом случае мы можем перенести принятый ответ. Может быть, OP сможет это сделать. Фактический ответ на вопрос, почему он использует этот метод, находится на полпути вниз. Хотя мне очень нравится повышение числа повторений, которое я получаю примерно раз в неделю благодаря этому ответу. - person Kibbee; 09.09.2011
comment
@Kibbee - спасибо, что подняли мой ответ. Полагаю, ОП может изменить принятый ответ по своему усмотрению? - person Ostemar; 25.09.2011
comment
-1 за утверждение, что это лучший алгоритм. - Учитывая случайную выборку чисел с использованием банковского округления, вы получите больше чисел на четных позициях, чем на нечетных. - Только после того, как вы усредните эти числа, вы снова получите спред, аналогичный исходному распределению. - Однако, если вы, например, нанесете эти данные на диаграмму рассеяния, можно будет увидеть искусственную группировку. - person paul23; 09.11.2017
comment
Для будущих посетителей ответ Данго (stackoverflow.com/a/7360463/1204599) хорошо дополняет этот ответ и дает отличный пример, демонстрирующий, как опция MidpointRounding.ToEven может дать более точные результаты. - person Mr.Z; 29.03.2018

Другие ответы с указанием причин, по которым алгоритм Банкира (также известный как с округлением от половины до четности) хороший выбор вполне правильный. Он не страдает от отрицательного или положительного смещения в такой степени, как округление на половину от нуля метод по большинству разумных распределений.

Но возник вопрос, почему .NET использует фактическое округление Banker по умолчанию - и ответ заключается в том, что Microsoft следовала Стандарт IEEE 754. Это также упоминается в MSDN for Math.Round. в разделе "Примечания".

Также обратите внимание, что .NET поддерживает альтернативный метод, указанный IEEE, путем предоставления перечисления MidpointRounding. Конечно, они могли бы предоставить дополнительные альтернативы для решения проблем, но они предпочитают просто выполняйте стандарт IEEE.

person Ostemar    schedule 03.07.2011
comment
Итак, почему IEEE 754 следует за округлением банкиров? Этот (все еще хороший) ответ просто неуместен. - person Henk Holterman; 14.03.2013
comment
@HenkHolterman Вероятно, из-за того, что упоминается в других ответах (и, как я резюмировал); он не страдает (слишком сильно) от отрицательного или положительного смещения и, как таковой, является более приемлемым значением по умолчанию для большинства распределений и проблемных областей. - person Ostemar; 12.04.2013
comment
Я думаю, это интересно, потому что IEEE 754 является стандартом для чисел с плавающей запятой, а Decimal - нет. Возможно, он следует IEEE 754, поэтому тот же алгоритм используется для округления Double as Decimal. - person Brandon Barkley; 03.02.2016
comment
@BrandonBarkley A Decimal или decimal является числом с плавающей запятой, и IEEE 754 действительно включает десятичные числа с плавающей запятой. - person Pablo H; 23.03.2018
comment
Интересно. Я не особо задумывался, как отображаются и хранятся десятичные числа. - person Brandon Barkley; 21.05.2018
comment
@HenkHolterman: или можно было бы возмутиться, что Microsoft пропустил ведро - person MestreLion; 26.10.2018
comment
Я попал сюда после того, как кто-то еще предложил стандарт IEEE, который, как я обнаружил, включает в себя все 5 определений округления, а вики-страница, связанная с 754, больше не содержит упомянутый раздел и не ссылается на округление до четного, только ссылается на добавление округления с нуля. - person Captain Prinny; 06.02.2020

Хотя я не могу ответить на вопрос «Почему дизайнеры Microsoft выбрали это по умолчанию?», Я просто хочу указать, что дополнительная функция не нужна.

Math.Round позволяет указать _ 2_:

  • ToEven - когда число находится на полпути между двумя другими, оно округляется до ближайшего четного числа.
  • AwayFromZero - когда число находится на полпути между двумя другими, оно округляется до ближайшего числа, которое отличается от нуля.
person Michael Stum    schedule 22.11.2008
comment
И, как я уже упоминал в связанных темах, убедитесь, что вы последовательно округляете - если вы иногда выполняете округление в базе данных, а иногда и в .net, у вас будут странные ошибки в один цент, на которые у вас уйдут недели. Вычислять. - person chris; 06.03.2009
comment
Однажды клиент заплатил мне более 40 000 долларов, чтобы я отыскал ошибку округления в 0,11 доллара между двумя числами, которые не превышали 1 МИЛЛИАРДА долларов; 0,11 доллара США было связано с разницей в ошибке округления 8-го разряда между мэйнфреймом и SQL Server. Поговорим о перфекционисте! - person E.J. Brennan; 22.01.2010
comment
@EJB - я бы, наверное, был перфекционистом, если бы имел дело с миллиардом долларов ;-) - person royse41; 10.09.2010
comment
@ E.J. Бреннан: Вам потребовалось 40 тысяч долларов, чтобы понять это? Я постоянно вижу подобные проблемы: округление - причина №1, нормализация с плавающей запятой - причина №2, ошибка программиста №3 - №3 может быть немедленно установлена ​​на №1, если нет предопределенных тестовых случаев. Между прочим, не могли бы вы связаться со своим миллиардным клиентом, я думаю, что смогу найти еще несколько ошибок за 40 тысяч долларов в его системе! : D - person ; 08.03.2011
comment
@seanxe: Или если бы вы видели Office Space. Серьезно, всякий раз, когда вы видите загадочные крошечные неточности в деньгах, почти всегда полезно разгадать тайну того, как именно они происходят. Возможно, вы решите не исправлять ошибки, но знание первопричины все еще имеет значение. Готов поспорить, многие люди, которые работают с деньгами, рады заметить даже крошечные неточности. - person Brian; 20.06.2011
comment
Почему дизайнеры Microsoft выбрали это по умолчанию? Они этого не сделали; IEEE сделал. См. Ответ Остемара: stackoverflow.com/a/6562018/385844 (извините за язвительные формулировки, но если бы у меня был никель за все разработчики, которых я встречал, которые думали, что у Microsoft есть ошибка в их процедурах округления ...) - person phoog; 03.12.2011
comment
@Matthieu N .: Может быть, его почасовая ставка составляет 80 тысяч долларов. :) - person mskfisher; 20.04.2012
comment
@ E.J.Brennan Обычно речь идет не о перфекционизме, а о (глупых, глупых) законах. Мне пришлось сделать то же самое (ошибки округления, связанные с использованием центов при оплате картой, но не наличными). Ошибка составляла около 0,1 доллара в год, но на ее исправление потребовался почти месяц (и клиент по-прежнему звонит нам время от времени, когда его пугают отчеты, в которых не отображаются округленные значения, а мне приходится объясните еще раз, почему это правильно, по логике и закону). Штраф за такую ​​ошибку в 0,1 доллара при появлении налогового инспектора? До 100 тысяч долларов. Большинство законов смехотворны :) - person Luaan; 13.02.2014

Десятичные дроби в основном используются для денег; Банковское округление - обычное дело при работе с деньгами. Или можно сказать.

В основном банкиры нуждаются в десятичном типе; поэтому он выполняет «банковское округление»

Преимущество банковского округления заключается в том, что в среднем вы получите тот же результат, если:

  • округлить набор «строк счета-фактуры» перед их сложением,
  • или сложите их, а затем округлите сумму

Округление перед суммированием сэкономило много работы до появления компьютеров.

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

person Ian Ringrose    schedule 22.01.2010
comment
Десятичные дроби в основном используются для денег ... и всего остального, кроме целого. - person John Tyree; 14.12.2013
comment
@JohnTyree, неверно большую часть времени используется double / float, когда это не целое число. см. stackoverflow.com/questions/2545567/ - person Ian Ringrose; 14.12.2013
comment
Вот это да. Нелепая ошибка с моей стороны. Decmials, да. Десятичных знаков, нет. Для потомков я согласен с первоначальным мнением здесь. - person John Tyree; 16.12.2013
comment
Банкирам может нравиться округление банкиров, но бухгалтеры могут не любить его, они говорят, что разница в 0,005 должна приводить к округлению на 0,01, независимо от того, четное или нечетное число. - person JustAMartin; 17.06.2016

Используйте другую перегрузку функции Round, например:

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

Будет выведено 3. И если вы используете

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

вы получите округление банкира.

person Omid Sadeghi    schedule 26.03.2017
comment
Это не отвечает на вопрос, почему Округление Банкира было выбрано по умолчанию. - person shortstuffsushi; 13.04.2017