Чому Math.Round (2.5) повертає 2 замість 3?


415

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

Це повинно бути 3, чи не так? Чому це 2 замість C #?


5
Це насправді особливість. Див. <a href=" msdn.microsoft.com/en-us/library/… MSDN документація</a>. Цей вид округлення відомий як округлення банкіра. Що стосується способу вирішення, існує <a href = " msdn. microsoft.com/en-us/library/… перевантаження </a>, що дозволяє абоненту вказати, як зробити округлення.
Джо

1
Мабуть, круглий метод, коли його запитують округлювати число точно між двома цілими числами, повертає парне ціле число. Отже, Math.Round (3.5) повертається 4. Дивіться цю статтю
Меттью Джонс

20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Роберт Дургін

SQL Server округляє таким чином; цікаві результати тестування, коли в T-SQL проводиться тестування тестування підрозділу C #.
idstam

7
@amed це не помилка. Це спосіб роботи бінарних плаваючих точок. 1.005не може бути представлений точно подвійно Це, напевно 1.00499.... Якщо ви скористаєтеся Decimalцією проблемою, вона зникне. Існування перевантаження Math.Round, яка займає кілька десяткових цифр удвічі, є сумнівним вибором дизайну IMO, оскільки він рідко працюватиме змістовно.
CodesInChaos

Відповіді:


560

По-перше, це не буде помилкою C # - це буде .NET помилка. C # - мова - вона не визначає, як Math.Roundреалізується.

По-друге, ні - якщо ви прочитаєте документи , ви побачите, що за замовчуванням округлення є "круглим до рівного" (округлення банкіра):


Тип поверненого значення : System.Double
Ціле число найближче a. Якщо дробова складова a знаходиться на півдорозі між двома цілими числами, одне з яких парне, а друге непарне, то повертається парне число. Зауважте, що цей метод повертає Doubleзамість інтегрального типу.

Зауваження
Поведінка цього методу відповідає стандарту 754 розділу IEEE, розділ 4. Цей вид округлення іноді називають округленням до найближчого або округленням банкіра. Це мінімізує помилки округлення, які виникають внаслідок послідовного округлення середнього значення в одному напрямку.

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

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

Можливо, вам буде цікаво поглянути на найближчий Java-еквівалент enum ( RoundingMode), який пропонує ще більше варіантів. (Це стосується не лише середніх точок.)


4
Я не знаю, чи це помилка, я думаю, що це було за дизайном, оскільки .5 наближається до найближчого найнижчого цілого числа, як і до найближчого найвищого цілого числа.
Стен Р.

3
Я пам'ятаю таку поведінку в VB до того, як застосував .NET.
Джон Фіала

7
Дійсно, стандарт IEEE 754, розділ 4, як зазначено в документації.
Джон Скіт

2
Мене це певний час опік і подумав, що це теж безумство. На щастя, вони додали спосіб уточнити округлення, про яке ми всі дізналися в класі; MidPointRounding.
Ши

26
+1 за "не очікувана поведінка [...] це кардинальний гріх в дизайні API"
BlueRaja - Danny Pflughoeft

215

Це називається округлення до рівного (або округлення банкіра), що є дійсною стратегією округлення для мінімізації нарахованих помилок у сумах (MidpointRounding.ToEven). Теорія полягає в тому, що якщо ви завжди округляєте 0,5 числа в одному напрямку, помилки накопичуватимуться швидше (круглий до парного, як передбачається, мінімізує це) (а) .

Перейдіть за цими посиланнями для описів 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
Також відомий як округлення банкіра
Pondidum

Гарне пояснення! Я хотів переконатися в тому, як накопичується помилка, і я написав сценарій, який показує, що значення, округлені за допомогою округлення банкіра, в кінцевому рахунку мають свої суми і в середньому набагато ближче до початкових значень. github.com/AmadeusW/RoundingDemo (фотографії сюжетів доступні)
Amadeusz Wieczorek

Незабаром після: чи не слід e(= 2.8) бути далі, ніж 2галочка?
superjos

Простий спосіб запам'ятати, припустивши, що десяте місце - 5: - одне місце і десяте місце - непарні = круглі вгору - одне місце і десяте місце змішані = округлення вниз * Нуль не непарне * Зворотне значення для від’ємних чисел
Аркем Енджел

@ArkhamAngel, що насправді здається складніше запам'ятати, ніж просто "зробити останню цифру рівним" :-)
paxdiablo

42

З MSDN Math.Round (подвійний а) повертає:

Ціле число найближче a. Якщо дробова складова a знаходиться на півдорозі між двома цілими числами, одне з яких парне, а друге непарне, то повертається парне число.

... і так 2,5, будучи на півдорозі між 2 і 3, округляється до парного числа (2). це називається «Округлення банкіра» (або «рівне») і є загальновживаним стандартом округлення.

Ця стаття MSDN:

Поведінка цього методу відповідає стандарту 754 IEEE, розділ 4. Цей тип округлення іноді називають округленням до найближчого, або округленням банкіра. Це мінімізує помилки округлення, які виникають внаслідок послідовного округлення середнього значення в одному напрямку.

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


37

Ви повинні перевірити MSDN на наявність Math.Round:

Поведінка цього методу відповідає стандарту 754 IEEE, розділ 4. Цей тип округлення іноді називають округленням до найближчого, або округленням банкіра.

Ви можете вказати поведінку Math.Roundвикористання перевантаження:

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

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2

31

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

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

З загального або «арифметичного» округлення зрозуміло, що 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 між округленням вниз і округленням.

"Випадкове округлення" округляє a .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. (в C #Round(AwayFromZero))


28

За замовчуванням MidpointRounding.ToEven, або округлення банкірів ( 2,5 стають 2, 4,5 стають 4 і так далі ) мене раніше зачепило написання звітів для бухгалтерського обліку, тож я напишу кілька слів того, що я дізнався раніше, і з того, щоб розглянути це повідомлення.

Хто ж ці банкіри, які збиваються на парні числа (можливо, британські банкіри!)?

З вікіпедії

Походження заокруглення терміна банкіри залишається більш неясним. Якщо цей метод округлення колись був стандартом у банківській справі, докази виявились надзвичайно важкими. Навпаки, розділ 2 доповіді Європейської Комісії «Введення євро та округлення валютних сум говорить про те, що раніше не існувало стандартного підходу до округлення в банківській справі; і він визначає, що "напівнаправлені" суми слід округлювати.

Дуже дивним є спосіб округлення, особливо для банківської справи, якщо, звичайно, банки не отримують багато депозитів із рівними сумами. Внесіть 2,4 млн фунтів стерлінгів, але ми назвемо це 2 млн фунтів стерлінгів, сер.

Стандарт 754 IEEE датується 1985 р. І дає обидва способи округлення, але з використанням банкіра, як рекомендовано стандартом. У цій статті у Вікіпедії є довгий перелік способів здійснення округлення (виправте мене, якщо хтось із наведених нижче є неправильним), і більшість не використовує банкірів, а округлення, якого ви навчаєте в школі:

  • C / C ++ раунд () від math.h заокруглює нуль (не округлення банкіра)
  • Java Math.Round округляється до нуля (він знижує результат, додає 0,5, кидає ціле число). Є альтернатива в BigDecimal
  • Perl використовує аналогічний спосіб C
  • Javascript такий же, як і Math.Round Java.

Спасибі за інформацію. Я ніколи цього не усвідомлював. Ваш приклад про мільйони висміює це трохи, але навіть якщо ви кружляєте на центах, сплата відсотків за 10 мільйонів банківських рахунків обійдеться банку чимало, якщо всі половини копійок закруглені, або коштуватимуть клієнтів багато, якщо всі півценти округлені вниз. Тож я можу собі уявити, що це узгоджений стандарт. Не впевнений, чи справді цим користуються банкіри. Більшість клієнтів не помітять округлення вниз, приносячи багато грошей, але я можу уявити, що це зобов’язане законами, якщо ви живете в країні зі зручними для клієнтів законами
Харальд Коппулз

15

Від MSDN:

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

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


3

Оскільки 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


3

У мене виникла ця проблема, коли мій сервер 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

Ця публікація має відповідь, яку ви шукаєте:

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

В основному це те, що сказано:

Повернене значення

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

Поведінка цього методу відповідає стандарту 754 IEEE, розділ 4. Цей тип округлення іноді називають округленням до найближчого, або округленням банкіра. Якщо цифр дорівнює нулю, такий тип округлення іноді називають округленням до нуля.


0

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/


-1

за допомогою спеціального округлення

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);
}

>.5виробляє таку саму поведінку, що і Math.Round. Питання про те, що трапляється, коли десяткова частина точно 0.5. Math.Round дозволяє вам вказати потрібний алгоритм округлення
Panagiotis Kanavos

-2

Це некрасиво, як все пекло, але завжди створює правильне арифметичне округлення.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}

5
Так само відбувається дзвінок Math.Roundта уточнення того, як ви хочете, щоб це було закруглено.
конфігуратор

-2

Ось як я повинен був це обробити:

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 з 2 десятковою комою, ви отримаєте 1.91, як очікувалося, але Math.Round(1.905,2,MidpointRounding.AwayFromZero)дасть 1.90! Метод Math.Round абсолютно непослідовний і непридатний для більшості основних проблем, з якими можуть зіткнутися програмісти. Я повинен перевірити, якщо (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)я не хочу закруглювати те, що повинно бути закругленим.


Math.Round(1.905,2,MidpointRounding.AwayFromZero)повернення1.91
Панайотис Канавос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.