Почему .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;
}

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

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

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

Дарин димитров
источник
105
Округление не является «более естественным». Природа не имеет к этому никакого отношения. Это просто то, что вы узнали в начальной школе, когда вы узнали понятие «округления». Уроки начальных классов не всегда дают полную картину.
Роб Кеннеди
45
@Rob И вот почему это более естественно , хотя и не правильно
Pacerier
10
Я не понимаю, @Pacerier. Я объяснил , почему это не естественно, а вы говорите , что это на самом деле , почему это является естественным. Как мой аргумент работает против моего заключения, которое противоположно вашему? Вещи, к которым вы привыкли, могут казаться естественными, и иногда мы образно говорим, что что-то «вторая натура», но это не делает их естественными.
Роб Кеннеди
16
@Rob Я говорю, что это естественно, потому что это кажется естественным. Вы знаете, что существует 36 различных объектов с одинаковым именем переменной, естественно ?
Pacerier
9
природа определенно аналог, так что это неправильное слово; но это педантично. Возможно, «обычное» было бы более подходящим словом для использования… «как обычно делают округления, которые делают люди»> 0,5 переходит к 1,0
Whytheq

Ответы:

196

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

Kibbee
источник
71
при условии, что у вас есть плоское распределение нечетных и четных входных данных, конечно,
jk.
7
+1 для лучшего алгоритма , хотя у Остемара есть фактический ответ ( stackoverflow.com/questions/311696/… )
Иан Бойд
2
@ Ян, я тоже даю ответ +1. В любом случае, мы можем получить «принятый ответ». Возможно, ОП сможет это сделать. Фактический ответ на вопрос «почему» использует этот метод - на полпути вниз по странице. Хотя мне очень нравится повторение, я получаю примерно один раз в неделю от этого ответа.
Кибби
@Kibbee - спасибо, что подтолкнул мой ответ. Я думаю, что это зависит от ОП, чтобы изменить принятый ответ, как он считает нужным?
Остемар
-1 для подтверждения, что это лучший алгоритм. - При случайной выборке числа с использованием округления банкира у вас будет больше чисел на четных позициях, чем на нечетных. - Только после усреднения этих чисел вы снова получаете спред, аналогичный исходному распределению. - Однако, если вы, например, разместите эти данные в точечной диаграмме, вы можете увидеть искусственную группировку.
paul23
437

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

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

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

Ostemar
источник
17
Итак, почему IEEE 754 следует за округлением банкиров? Этот (все еще хороший) ответ просто проходит мимо.
Хенк Холтерман
4
@HenkHolterman Вероятно из-за того, что упомянуто в других ответах (и, как я резюмировал); он не страдает (слишком сильно) от отрицательного или положительного смещения и, как таковой, делает его более приемлемым по умолчанию для большинства распределений и проблемных областей.
Остемар
Я думаю, что это интересно, потому что IEEE 754 является стандартом для чисел с плавающей запятой, десятичного числа которых нет. Может быть, это следует IEEE 754, так что тот же алгоритм используется для округления Double до Decimal.
Брэндон Баркли
1
@BrandonBarkley Десятичное или десятичное число - это число с плавающей запятой, а IEEE 754 содержит десятичные числа с плавающей запятой.
Пабло Х
88

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

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

  • ToEven - Когда число находится посередине между двумя другими, оно округляется до ближайшего четного числа.
  • AwayFromZero - когда число находится на полпути между двумя другими, оно округляется до ближайшего числа, которое отличается от нуля.
Майкл Стум
источник
8
И как я упоминал в связанных темах, убедитесь, что вы последовательны в своем округлении - если вы иногда делаете округление в базе данных, а иногда и в .net, у вас будут странные ошибки в один цент, которые займут у вас недели Вычислять.
Крис
59
Однажды клиент заплатил мне более 40 000 долларов, чтобы отследить ошибку округления в 0,11 доллара между двумя числами, которые оба просто стеснялись 1 млрд долларов; $ 0,11 был вызван разницей в ошибке округления 8-ой цифры между мэйнфреймом и SQL Server. Поговорим о перфекционисте!
Э.Дж. Бреннан
12
@EJB - я бы был перфекционистом, если бы имел дело с миллиардом долларов ;-)
royse41
12
@EJ Brennan: Тебе понадобилось 40 тысяч долларов, чтобы понять это? Я вижу подобные проблемы постоянно, округление - это причина № 1, двойная / плавающая нормализация - причина № 2, ошибка программиста № 3 - № 3 может быть немедленно установлена ​​на № 1, если нет предопределенных тестовых случаев. Кстати, не могли бы вы поставить меня в контакт с вашим миллиардером, я думаю, я мог бы найти еще несколько ошибок в $ 40 000 в его системе! : D
3
@seanxe: Или если бы вы видели Office Space. Серьезно, всякий раз, когда вы видите таинственные, крошечные неточности в деньгах, разгадать тайну, как именно они происходят, почти всегда хорошая идея. Возможно, вы решите не исправлять ошибки, но зная причину все еще имеет значение. Бьюсь об заклад, многие люди, которые работают с деньгами, рады заметить даже крошечные неточности.
Брайан
22

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

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

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

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

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

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

Ян Рингроз
источник
8
«Десятичные дроби в основном используются для денег» ... и все остальное, что не является целым числом.
Джон Тайри
3
@JohnTyree, Неверно, чаще всего используется двойное число / число с плавающей запятой, когда оно не является целым числом. см stackoverflow.com/questions/2545567/...
Ian Рингроуза
3
Вот это да. Смешная ошибка с моей стороны. Decmialс, да. Десятичных знаков нет Для потомков я согласен с первоначальным мнением здесь.
Джон Тайри
Банкирам может понравиться округление банкиров, но бухгалтеры могут не быть его поклонниками, они говорят, что разница в 0,005 должна привести к округлению на 0,01, независимо от того, является ли оно нечетным или четным числом.
JustAMartin
0

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

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

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

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

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

Омид Садеги
источник
4
Это не отвечает на вопрос, почему округление банкира было выбрано по умолчанию.
shortstuffsushi