Почему.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 ответов

Решение

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

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

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

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

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

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

  • ToEven - Когда число находится посередине между двумя другими, оно округляется до ближайшего четного числа.
  • 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
Другие вопросы по тегам