Почему Math.Round(2.5) возвращает 2 вместо 3?

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

Предполагается, что это 3, не так ли? Почему это 2 вместо С#?

Ответ 1

Во-первых, это не будет ошибкой С# в любом случае - это будет ошибка .NET. С# - это язык - он не решает, как реализовать Math.Round.

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

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

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

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

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

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

Ответ 2

Это называется округлением до четного (или округления банкиров), что является действительной стратегией округления для минимизации начисленных ошибок в суммах (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,...).

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

Ответ 3

Из MSDN, Math.Round(double a) возвращает:

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

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

В той же статье MSDN:

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

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

Ответ 4

Вы должны проверить 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

Ответ 5

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

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

В общем или "арифметическом" округлении ясно, что 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 между округлением и округлением.

"Случайное округление" округляет 0,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. (в С# Round(AwayFromZero))

Ответ 6

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

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

Из википедии

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

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

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

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

Ответ 7

Из MSDN:

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

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

Ответ 8

Поскольку 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

Ответ 9

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

Здесь выполняется реализация с 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

Ответ 10

У этой записи есть ответ, который вы ищете:

http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx

В основном это то, что он говорит:

Возвращаемое значение

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

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

Ответ 11

Silverlight не поддерживает параметр MidpointRounding. Здесь используется метод расширения для Silverlight, который добавляет перечисление MidpointRounding:

public enum MidpointRounding
{
    ToEven,
    AwayFromZero
}

public static class DecimalExtensions
{
    public static decimal Round(this decimal d, MidpointRounding mode)
    {
        return d.Round(0, mode);
    }

    /// <summary>
    /// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
    /// </summary>
    /// <param name="d">A Decimal number to be rounded.</param>
    /// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
    /// <returns>The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned.</returns>
    public static decimal Round(this decimal d, int decimals, MidpointRounding mode)
    {
        if ( mode == MidpointRounding.ToEven )
        {
            return decimal.Round(d, decimals);
        }
        else
        {
            decimal factor = Convert.ToDecimal(Math.Pow(10, decimals));
            int sign = Math.Sign(d);
            return Decimal.Truncate(d * factor + 0.5m * sign) / factor;
        }
    }
}

Источник: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/

Ответ 12

с помощью пользовательского округления

public int Round(double value)
{
    double decimalpoints = Math.Abs(value - Math.Floor(value));
    if (decimalpoints > 0.5)
        return (int)Math.Round(value);
    else
        return (int)Math.Floor(value);
}

Ответ 13

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

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

Ответ 14

Вот как я должен был это сделать:

Public Function Round(number As Double, dec As Integer) As Double
    Dim decimalPowerOfTen = Math.Pow(10, dec)
    If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then
        Return Math.Round(number, 2, MidpointRounding.AwayFromZero)
    Else
        Return CInt(number * decimalPowerOfTen + 0.5) / 100
    End If
End Function

Попытка с 1.905 с двумя десятичными знаками даст 1.91, как ожидалось, но Math.Round(1.905,2,MidpointRounding.AwayFromZero) дает 1.90! Метод Math.Round абсолютно несогласован и непригоден для большинства проблем, с которыми могут столкнуться программисты. Я должен проверить, если (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2) причина, я не хочу округлять, что должно быть округлено вниз.

Ответ 15

Это уродливо, как и все ад, но всегда производит правильное арифметическое округление.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

  numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');

  return double.Parse(number.ToString(numberFormat));

}