Почему .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.
Решение
Возможно, потому что это лучший алгоритм. В течение многих выполненных округлений вы в среднем получите, что все .5 округляются одинаково вверх и вниз. Это дает более точные оценки фактических результатов, если вы, например, добавляете несколько округленных чисел. Я бы сказал, что хотя это и не то, что некоторые могут ожидать, это, вероятно, более правильная вещь.
Другие советы
Другой отвечает причинами, по которым алгоритм Банкира (он же округляет половину до четного ) это хороший выбор, вполне правильный. Он не страдает от отрицательного или положительного смещения так сильно, как метод округления на половину от нуля по наиболее разумным распределениям.
Но вопрос был в том, почему .NET по умолчанию использует фактическое округление Banker - и ответ заключается в том, что Microsoft следовала стандарт IEEE 754 . Это также упоминается в MSDN для Math.Round в разделе Примечания.
Также обратите внимание, что .NET поддерживает альтернативный метод, определенный IEEE, предоставляя перечисление MidpointRounding
. Конечно, они могли бы предоставить больше альтернатив для решения связей, но они просто решили выполнить стандарт IEEE.
Хотя я не могу ответить на вопрос "Почему разработчики Microsoft выбрали это по умолчанию?", я просто хочу отметить, что дополнительная функция не нужна.
Math.Round
позволяет вам указать MidpointRounding
:
- Четное - когда число находится на полпути между двумя другими, оно округляется в сторону ближайшего четного числа.
- AwayFromZero - когда число находится на полпути между двумя другими, оно округляется в сторону ближайшего числа, которое находится вдали от нуля.
Десятичные дроби в основном используются для Деньги;банковское округление является обычным явлением при работе с Деньги.Или вы могли бы сказать.
Это в основном банкиры, что нужно десятичное тип;следовательно, это делает “банковское округление”
Преимущество банковского округления в том, что в среднем вы получите тот же результат, если:
- округлите набор "строк счета” перед их суммированием,
- или сложите их, а затем округлите общую сумму
Округление перед суммированием экономило много работы в те дни, когда еще не было компьютеров.
(В Великобритании, когда мы перешли на десятичную систему счисления, банки не имели дела с половиной пенса, но в течение многих лет все еще существовала монета в полпенса, и в магазинах часто цены заканчивались на половину пенса – так что было много округлений)
Используйте другую перегрузку функции Round, как это:
decimal.Round(2.5m, 0,MidpointRounding.AwayFromZero)
Будет выведено 3 . И если вы используете
decimal.Round(2.5m, 0,MidpointRounding.ToEven)
вы получите банковское округление.
Кроме того, обратите внимание, что «округление» через строку формата (например, «0») получается результат, отличный от «Math.Round ()». А именно, что 5, .5 и т. Д. Всегда округляются:
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