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

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

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

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

Ответы

Ответ 1

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

Ответ 2

Другие ответы на причины, по которым алгоритм Banker (aka круглый пол до четного), является хорошим выбором. Он не страдает от отрицательного или положительного уклона так же, как round half away from zero для большинства разумных распределений.

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

  • вокруг набора "строк счета" перед добавлением их,
  • или добавьте их вокруг общей суммы

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

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

Ответ 5

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

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

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

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

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

Ответ 6

Также обратите внимание, что "округление" через строку формата (например, "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