Почему Math.Round (2.5) возвращает 2 вместо 3? - PullRequest
378 голосов
/ 10 июня 2009

В C # результат Math.Round(2.5) равен 2.

Это должно быть 3, не так ли? Почему вместо 2 в C #?

Ответы [ 15 ]

530 голосов
/ 10 июня 2009

Во-первых, это не ошибка C #, а ошибка .NET. C # - это язык - он не решает, как реализован Math.Round.

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

Возвращаемое значение
Тип: System.Double
Целое число, ближайшее к a. Если Дробная составляющая является на полпути между двумя целыми числами, одно из которых четный и другой нечетный, то четный номер возвращается. Обратите внимание, что это метод возвращает Double вместо целочисленный тип.

Примечания
Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называется округлением до ближайшего, или банковское округление. Минимизирует ошибки округления, возникающие в результате последовательно округляя значение средней точки в одном направлении.

Вы можете указать, как Math.Round округлять средние точки, используя перегрузку , которая принимает значение MidpointRounding. Существует одна перегрузка с MidpointRounding, соответствующая каждой из перегрузок, у которой ее нет:

Правильно ли выбран этот вариант по умолчанию или нет, это другой вопрос. (MidpointRounding был представлен только в .NET 2.0. До этого я не был уверен, что существовал какой-либо простой способ реализовать желаемое поведение, не делая его самостоятельно.) В частности, история показала, что это не ожидаемый поведение - и в большинстве случаев это кардинальный грех в дизайне API. Я вижу , почему Банковское округление полезно ... но для многих это все еще сюрприз.

Вам может быть интересно взглянуть на ближайшее эквивалентное перечисление Java (RoundingMode), которое предлагает еще больше возможностей. (Это касается не только средних точек.)

195 голосов
/ 11 мая 2009

Это называется округлением до четного (или банковским округлением), которое является допустимой стратегией округления для минимизации накопленных ошибок в суммах (MidpointRounding.ToEven). Теория состоит в том, что, если вы всегда округляете число 0,5 в одном и том же направлении, ошибки будут накапливаться быстрее (округление до четного должно минимизировать это) (a) .

Перейдите по этим ссылкам для описания MSDN:

  • Math.Floor, которая округляется до отрицательной бесконечности.
  • Math.Ceiling, что округляет в сторону увеличения до бесконечности.
  • Math.Truncate, который округляется в большую или меньшую сторону до нуля.
  • Math.Round, который округляется до ближайшего целого или указанного числа десятичных разрядов. Вы можете указать поведение, если оно точно равноудалено между двумя возможностями, такими как округление, чтобы конечная цифра была четной («Round(2.5,MidpointRounding.ToEven)» становилась 2) или чтобы она была дальше от нуля («Round(2.5,MidpointRounding.AwayFromZero)» становится 3).

Может помочь следующая диаграмма и таблица:

-3        -2        -1         0         1         2         3
 +--|------+---------+----|----+--|------+----|----+-------|-+
    a                     b       c           d            e

                       a=-2.7  b=-0.5  c=0.3  d=1.5  e=2.8
                       ======  ======  =====  =====  =====
Floor                    -3      -1      0      1      2
Ceiling                  -2       0      1      2      3
Truncate                 -2       0      0      1      2
Round(ToEven)            -3       0      0      2      3
Round(AwayFromZero)      -3      -1      0      2      3

Обратите внимание, что Round намного мощнее, чем кажется, просто потому, что он может округляться до определенного количества десятичных разрядов. Все остальные всегда округляются до нуля. Например:

n = 3.145;
a = System.Math.Round (n, 2, MidpointRounding.ToEven);       // 3.14
b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15

С другими функциями вы должны использовать умножение / деление для достижения того же эффекта:

c = System.Math.Truncate (n * 100) / 100;                    // 3.14
d = System.Math.Ceiling (n * 100) / 100;                     // 3.15

(a) Конечно, эта теория зависит от того факта, что ваши данные имеют довольно четный разброс значений по четным половинкам (0,5, 2,5, 4,5, ...) и нечетным половинкам ( 1,5, 3,5, ...).

Если все"половинные значения" являются четными (например), ошибки будут накапливаться так же быстро, как если бы вы всегда округлились.

42 голосов
/ 10 июня 2009

С MSDN, Math.Round (удваивается) возвращает:

Целое число, ближайшее к. Если Дробная составляющая является на полпути между двумя целыми числами, одно из которых четный и другой нечетный, то четный номер возвращается.

... и так, 2.5, находясь на полпути между 2 и 3, округляется до четного числа (2). это называется Банковское округление (или округление до четного) и является широко используемым стандартом округления.

Та же статья MSDN:

Поведение этого метода следующее Стандарт IEEE 754, раздел 4. Это вид округления иногда называют округление до ближайшего или банковского округления. Минимизирует ошибки округления это результат последовательного округления значение средней точки в одном направление.

Вы можете указать другое поведение округления, вызвав перегрузки Math.Round, которые принимают режим MidpointRounding.

37 голосов
/ 10 июня 2009

Вы должны проверить MSDN для Math.Round:

Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира.

Вы можете указать поведение Math.Round, используя перегрузку:

Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2
31 голосов
/ 11 мая 2009

Характер округления

Рассмотрим задачу округления числа, которое содержит дробь, скажем, до целого числа. В этом случае процесс округления состоит в том, чтобы определить, какое целое число лучше всего представляет число, которое вы округляете.

При обычном или «арифметическом» округлении ясно, что 2,1, 2,2, 2,3 и 2,4 округляются до 2,0; и 2,6, 2,7, 2,8 и 2,9-3,0.

Это оставляет 2,5, что не ближе к 2,0, чем к 3,0. Вы можете выбрать между 2,0 и 3,0, в любом случае будет одинаково.

Для минус чисел -2,1, -2,2, -2,3 и -2,4 станет -2,0; и -2,6, 2,7, 2,8 и 2,9 при арифметическом округлении станут -3,0.

Для -2,5 необходим выбор между -2,0 и -3,0.

Другие формы округления

«Округление» берет любое число с десятичными знаками и делает его следующим «целым» числом. Таким образом, не только 2,5 и 2,6 округления до 3,0, но и 2,1 и 2,2.

Округление вверх сдвигает как положительные, так и отрицательные числа от нуля. Например. От 2,5 до 3,0 и от -2,5 до -3,0.

«Округление вниз» обрезает числа, отсекая ненужные цифры. Это имеет эффект перемещения чисел к нулю. Например. 2,5-2,0 и -2,5-2,0

В «округлении банкира» - в его наиболее распространенной форме - округляемое число .5 округляется либо вверх, либо вниз, так что результатом округления всегда является четное число. Таким образом, 2,5 округления до 2,0, от 3,5 до 4,0, от 4,5 до 4,0, от 5,5 до 6,0 и т. Д.

«Альтернативное округление» чередует процесс для любого .5 между округлением вниз и округлением вверх.

«Случайное округление» округляет .5 вверх или вниз на совершенно случайной основе.

Симметрия и асимметрия

Функция округления называется «симметричной», если она либо округляет все числа от нуля, либо округляет все числа до нуля.

Функция является «асимметричной», если округляет положительные числа до нуля и отрицательные числа от нуля. Например. От 2,5 до 2,0; и от -2,5 до -3,0.

Асимметричная функция также округляет положительные числа от нуля и отрицательные числа до нуля. Например. От 2,5 до 3,0; и от -2,5 до -2,0.

В большинстве случаев люди думают о симметричном округлении, где -2,5 будет округлено до -3,0, а 3,5 округлено до 4,0. *

24 голосов
/ 22 октября 2009

Значение по умолчанию MidpointRounding.ToEven, или округление Банкиров ( 2.5 становится 2, 4.5 становится 4 и т. Д. ), прежде меня задело написанием отчетов для учета, поэтому я напишу несколько слов то, что я узнал, ранее и от просмотра этого поста.

Кто эти банкиры, которые округляют до четных чисел (возможно, британские банкиры!)?

Из википедии

Происхождение термина банкиры округление остается более неясным. Если это метод округления всегда был стандартом в банковское дело, доказательства доказали очень трудно найти. К напротив, раздел 2 европейской Отчет комиссии Введение евро и округление валюты Суммы свидетельствуют о том, что там было ранее не было стандартного подхода к округлению в банковском деле; и это указывает, что "на полпути" суммы должно быть округлено.

Кажется, это очень странный способ округления, особенно для банковской деятельности, если, конечно, банки не используют для получения большого количества депозитов четных сумм. Внесите 2,4 млн. Фунтов стерлингов, но мы назовем это 2 млн. Фунтов стерлингов.

Стандарт IEEE 754 датируется 1985 годом и дает оба способа округления, но с банкиром в соответствии с рекомендациями стандарта. Эта статья в википедии содержит длинный список того, как языки реализуют округление (поправьте меня, если какие-либо из нижеприведенных слов неверны), и большинство из них не использует банкиров, но округление, которому вас учили в школе:

  • C / C ++ round () от math.h округляет до нуля (не округление банкира)
  • Java Math.Round округляется от нуля (он суммирует результат, добавляет 0,5, приводит к целому числу). Есть альтернатива в BigDecimal
  • Perl использует способ, аналогичный C
  • Javascript - это то же самое, что и Java Math.Round.
15 голосов
/ 11 мая 2009

Из MSDN:

По умолчанию Math.Round использует MidpointRounding.ToEven. Большинство людей не знакомы с "округлением до даже «в качестве альтернативы» округления от нуля "чаще учил в школе. .NET по умолчанию "Округление до четного", как оно есть статистически лучше, потому что это не разделяет тенденцию "округление от нуля", чтобы округлить немного чаще, чем округляет вниз (при условии, что цифры округлые имеют тенденцию быть положительными.)

http://msdn.microsoft.com/en-us/library/system.math.round.aspx

3 голосов
/ 10 ноября 2011

Поскольку Silverlight не поддерживает опцию MidpointRounding, вы должны написать свою собственную. Что-то вроде:

public double RoundCorrect(double d, int decimals)
{
    double multiplier = Math.Pow(10, decimals);

    if (d < 0)
        multiplier *= -1;

    return Math.Floor((d * multiplier) + 0.5) / multiplier;

}

Примеры того, как использовать это как расширение, см. В сообщении: .NET и Silverlight Rounding

2 голосов
/ 07 декабря 2012

У меня была проблема, когда мой SQL-сервер округлялся до 0,5 к 1, а мое приложение C # - нет. Таким образом, вы увидите два разных результата.

Вот реализация с int / long. Вот как Java округляет.

int roundedNumber = (int)Math.Floor(d + 0.5);

Это, пожалуй, самый эффективный метод, о котором вы могли подумать.

Если вы хотите сохранить двойное значение и использовать десятичную точность, то на самом деле это просто вопрос использования показателей степени 10 на основе количества десятичных знаков.

public double getRounding(double number, int decimalPoints)
{
    double decimalPowerOfTen = Math.Pow(10, decimalPoints);
    return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen;
}

Вы можете ввести отрицательную десятичную дробь для десятичных точек, и это слово отлично.

getRounding(239, -2) = 200
0 голосов
/ 29 октября 2017

Простой способ:

Math.Ceiling(decimal.Parse(yourNumber + ""));
...