Это на самом деле особенность. См. <a href=" msdn.microsoft.com/en-us/library/… документацию MSDN</a> . Этот вид округления известен как банковское округление. Что касается обходного пути, существует <a href = " msdn. microsoft.com/en-us/library/… overload </a>, что позволяет вызывающей стороне указать, как выполнить округление.
Джо
1
Очевидно, что метод округления, когда его просят округлить число ровно между двумя целыми числами, возвращает четное целое число. Итак, Math.Round (3.5) возвращает 4. Смотрите эту статью
SQL Server округляется таким образом; интересные результаты тестов, когда есть модульный тест C # для проверки округления в T-SQL.
Идстам
7
@Med это не ошибка. Это способ работы двоичных чисел с плавающей запятой. 1.005не может быть представлен точно в двойном Это наверное 1.00499.... Если вы используете Decimalэту проблему, исчезнет. Существование перегрузки Math.Round, которая принимает число десятичных цифр в двойном числе, является сомнительным вариантом выбора IMO, поскольку она редко будет работать значимым образом.
CodesInChaos
Ответы:
561
Во-первых, в любом случае это не ошибка C #, а ошибка .NET. C # - это язык - он не решает, как Math.Roundэто реализовано.
И, во-вторых, нет - если вы прочитаете документы , вы увидите, что по умолчанию используется округление «до четного» (округление банкира):
Возвращаемое значение Тип: System.Double Целое число, ближайшее к. Если дробный компонент а находится посередине между двумя целыми числами, одно из которых является четным, а другое нечетным, то возвращается четное число. Обратите внимание, что этот метод возвращает Doubleвместо целочисленного типа.
Заметки Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира. Это сводит к минимуму ошибки округления, возникающие в результате последовательного округления значения средней точки в одном направлении.
Вы можете указать, как Math.Roundследует округлять средние точки, используя перегрузку, которая принимает MidpointRoundingзначение. Существует одна перегрузка, MidpointRoundingсоответствующая каждой из перегрузок, у которой ее нет:
Был ли этот стандарт выбран правильно или нет - это другой вопрос. ( MidpointRoundingБыл представлен только в .NET 2.0. До этого я не был уверен, что был какой-либо простой способ реализовать желаемое поведение, не делая его самостоятельно.) В частности, история показала, что это не ожидаемое поведение - и в большинстве случаев это кардинальный грех в дизайне API. Я понимаю, почему округление Банкира полезно ... но для многих это все еще сюрприз.
Возможно, вам будет интересно взглянуть на ближайший эквивалент Java enum ( RoundingMode), который предлагает еще больше опций. (Это касается не только средних точек.)
я не знаю, является ли это ошибкой, я думаю, что это было разработано, так как .5 так же близко к ближайшему младшему целому числу, как и к ближайшему наибольшему целому числу.
Стэн Р.
3
Я помню это поведение в VB до применения .NET.
Джон Фиала
7
Действительно, стандарт IEEE 754, раздел 4, как указано в документации.
Джон Скит
2
Я был сожжен этим некоторое время назад и подумал, что это было просто безумие. К счастью, они добавили способ указать округление, которое все мы изучали в начальной школе; MidPointRounding.
Ши
26
+1 за «это не ожидаемое поведение [...], это
основной
215
Это называется округлением до четного (или округлением банкира), которое является допустимой стратегией округления для минимизации накопленных ошибок в суммах (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-10123+--|------+---------+----|----+--|------+----|----+-------|-+
a b c d e
a=-2.7 b=-0.5 c=0.3 d=1.5 e=2.8===========================Floor-3-1012Ceiling-20123Truncate-20012Round(ToEven)-30023Round(AwayFromZero)-3-1023
Обратите внимание, что 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, ...).
Если все «половинные значения» являются четными (например), ошибки будут накапливаться так же быстро, как если бы вы всегда округляли.
Хорошее объяснение! Я хотел убедиться, как накапливается ошибка, и написал скрипт, который показывает, что значения, округленные с использованием округления банкира, в конечном счете, имеют свои суммы и средние значения намного ближе к исходным значениям. github.com/AmadeusW/RoundingDemo (доступны фотографии)
Amadeusz Wieczorek
Через некоторое время: не должен ли eтик (= 2,8) быть правее 2тика?
Superjos
Простой способ запомнить, и предположить, что десятое место равно 5: - одно место и десятое место все нечетные = округление вверх - одно место и десятое место смешаны = округление вниз * Ноль не странно * Перевернуто для отрицательных чисел
Arkham Angel
@ArkhamAngel, что на самом деле , кажется , труднее запомнить , чем просто «сделать последнюю цифру даже» :-)
Целое число, ближайшее к. Если дробный компонент а находится посередине между двумя целыми числами, одно из которых является четным, а другое нечетным, то возвращается четное число.
... и поэтому 2.5, находясь на полпути между 2 и 3, округляется до четного числа (2). это называется округлением банкира (или округлением до четности) и является широко используемым стандартом округления.
Та же статья MSDN:
Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира. Это сводит к минимуму ошибки округления, возникающие в результате последовательного округления значения средней точки в одном направлении.
Вы можете указать другое поведение округления, вызвав перегрузки Math.Round, которые принимают MidpointRoundingрежим.
Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира.
Вы можете указать поведение Math.Roundиспользования перегрузки:
Рассмотрим задачу округления числа, которое содержит дробь, скажем, до целого числа. В этом случае процесс округления состоит в том, чтобы определить, какое целое число лучше всего представляет число, которое вы округляете.
При обычном или «арифметическом» округлении ясно, что 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
В «округлении банкира» - в его наиболее распространенной форме - округляемое число 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. (в C #Round(AwayFromZero))
По умолчанию 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 есть альтернатива
Спасибо за информацию. Я никогда не осознавал этого. Ваш пример о миллионах высмеивает это немного, но даже если вы округлите центы, то выплата процентов по 10 миллионам банковских счетов обойдется банку дорого, если все полценты будут округлены, или будет стоить клиентам много, если все полцента округляются в меньшую сторону. Так что я могу представить, что это согласованный стандарт. Не уверен, действительно ли это используется банкирами. Большинство клиентов не заметят округления, принося много денег, но я могу себе представить, что это обязательно по законам, если вы живете в стране с законами,
удобными для
15
Из MSDN:
По умолчанию Math.Round использует MidpointRounding.ToEven. Большинство людей не знакомы с «округлением до четности» как альтернативой, «округление от нуля» чаще всего преподается в школе. По умолчанию .NET имеет значение «Округление до четности», поскольку оно статистически лучше, поскольку оно не разделяет тенденцию «округления от нуля» к округлению немного чаще, чем округление вниз (при условии, что округляемые числа имеют тенденцию быть положительными. )
У меня была проблема, когда мой SQL-сервер округлялся до 0,5 к 1, а мое приложение на C # - нет. Таким образом, вы увидите два разных результата.
Вот реализация с int / long. Вот как Java округляется.
int roundedNumber =(int)Math.Floor(d +0.5);
Это, вероятно, самый эффективный метод, о котором вы могли подумать.
Если вы хотите сохранить двойное значение и использовать десятичную точность, то на самом деле это просто вопрос использования показателей 10 в зависимости от количества десятичных знаков.
Число ближайшего значения с точностью до цифр. Если значение находится посередине между двумя числами, одно из которых четное, а другое нечетное, возвращается четное число. Если точность значения меньше цифры, то значение возвращается без изменений.
Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира. Если цифры равны нулю, этот вид округления иногда называется округлением до нуля.
Silverlight не поддерживает параметр MidpointRounding. Вот метод расширения для Silverlight, который добавляет перечисление MidpointRounding:
publicenumMidpointRounding{ToEven,AwayFromZero}publicstaticclassDecimalExtensions{publicstaticdecimalRound(thisdecimal 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>publicstaticdecimalRound(thisdecimal d,int decimals,MidpointRounding mode){if( mode ==MidpointRounding.ToEven){returndecimal.Round(d, decimals);}else{decimal factor =Convert.ToDecimal(Math.Pow(10, decimals));int sign =Math.Sign(d);returnDecimal.Truncate(d * factor +0.5m* sign)/ factor;}}}
>.5производит то же поведение, что и Math.Round. Вопрос в том, что происходит, когда десятичная часть точно 0.5. Math.Round позволяет указать желаемый алгоритм округления
Panagiotis Kanavos
-2
Это ужасно, как и весь ад, но всегда производит правильное арифметическое округление.
Попытка с 1,905 с двумя десятичными знаками даст 1,91, как и ожидалось, но Math.Round(1.905,2,MidpointRounding.AwayFromZero)дает 1,90! Метод Math.Round абсолютно несовместим и непригоден для большинства основных проблем, с которыми могут столкнуться программисты. Я должен проверить, если (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)я не хочу округлять то, что должно быть округлено.
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
1.005
не может быть представлен точно в двойном Это наверное1.00499...
. Если вы используетеDecimal
эту проблему, исчезнет. Существование перегрузки Math.Round, которая принимает число десятичных цифр в двойном числе, является сомнительным вариантом выбора IMO, поскольку она редко будет работать значимым образом.Ответы:
Во-первых, в любом случае это не ошибка C #, а ошибка .NET. C # - это язык - он не решает, как
Math.Round
это реализовано.И, во-вторых, нет - если вы прочитаете документы , вы увидите, что по умолчанию используется округление «до четного» (округление банкира):
Вы можете указать, как
Math.Round
следует округлять средние точки, используя перегрузку, которая принимаетMidpointRounding
значение. Существует одна перегрузка,MidpointRounding
соответствующая каждой из перегрузок, у которой ее нет:Round(Decimal)
/Round(Decimal, MidpointRounding)
Round(Double)
/Round(Double, MidpointRounding)
Round(Decimal, Int32)
/Round(Decimal, Int32, MidpointRounding)
Round(Double, Int32)
/Round(Double, Int32, MidpointRounding)
Был ли этот стандарт выбран правильно или нет - это другой вопрос. (
MidpointRounding
Был представлен только в .NET 2.0. До этого я не был уверен, что был какой-либо простой способ реализовать желаемое поведение, не делая его самостоятельно.) В частности, история показала, что это не ожидаемое поведение - и в большинстве случаев это кардинальный грех в дизайне API. Я понимаю, почему округление Банкира полезно ... но для многих это все еще сюрприз.Возможно, вам будет интересно взглянуть на ближайший эквивалент Java enum (
RoundingMode
), который предлагает еще больше опций. (Это касается не только средних точек.)источник
Это называется округлением до четного (или округлением банкира), которое является допустимой стратегией округления для минимизации накопленных ошибок в суммах
(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).Следующая диаграмма и таблица могут помочь:
Обратите внимание, что
Round
он намного мощнее, чем кажется, просто потому, что он может округляться до определенного количества десятичных знаков. Все остальные всегда округляются до нуля. Например:С другими функциями вы должны использовать умножение / деление для достижения того же эффекта:
(a) Конечно, эта теория зависит от того факта, что ваши данные имеют довольно четный разброс значений по четным половинкам (0,5, 2,5, 4,5, ...) и нечетным половинам (1,5, 3,5, ...).
Если все «половинные значения» являются четными (например), ошибки будут накапливаться так же быстро, как если бы вы всегда округляли.
источник
e
тик (= 2,8) быть правее2
тика?Из MSDN Math.Round (double a) возвращает:
... и поэтому 2.5, находясь на полпути между 2 и 3, округляется до четного числа (2). это называется округлением банкира (или округлением до четности) и является широко используемым стандартом округления.
Та же статья MSDN:
Вы можете указать другое поведение округления, вызвав перегрузки Math.Round, которые принимают
MidpointRounding
режим.источник
Вы должны проверить MSDN для
Math.Round
:Вы можете указать поведение
Math.Round
использования перегрузки:источник
Характер округления
Рассмотрим задачу округления числа, которое содержит дробь, скажем, до целого числа. В этом случае процесс округления состоит в том, чтобы определить, какое целое число лучше всего представляет число, которое вы округляете.
При обычном или «арифметическом» округлении ясно, что 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
В «округлении банкира» - в его наиболее распространенной форме - округляемое число 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. (в C #
Round(AwayFromZero)
)источник
По умолчанию
MidpointRounding.ToEven
, или округление банкиров ( 2.5 становится 2, 4.5 становится 4 и т. Д. ), Раньше меня ужалило при написании отчетов для бухгалтерии, поэтому я напишу несколько слов о том, что я узнал ранее и о том, эта почта.Кто эти банкиры, которые округляют до четных чисел (возможно, британские банкиры!)?
Из википедии
Это кажется очень странным способом округления, особенно для банковского дела, если, конечно, банки не используют для получения большого количества депозитов равных сумм. Внесите 2,4 млн. Фунтов стерлингов, но мы назовем это 2 млн. Фунтов стерлингов.
Стандарт IEEE 754 датируется 1985 годом и дает оба способа округления, но с банкиром в соответствии с рекомендациями стандарта. Эта статья в Википедии содержит длинный список того, как языки реализуют округление (поправьте меня, если что-то из нижеперечисленного неверно), и большинство не использует банкиров, но округление, которому вас учили в школе:
источник
Из MSDN:
http://msdn.microsoft.com/en-us/library/system.math.round.aspx
источник
Поскольку Silverlight не поддерживает опцию MidpointRounding, вы должны написать свою собственную. Что-то вроде:
Примеры, включающие, как использовать это как расширение, см. В посте: .NET и Silverlight Rounding
источник
У меня была проблема, когда мой SQL-сервер округлялся до 0,5 к 1, а мое приложение на C # - нет. Таким образом, вы увидите два разных результата.
Вот реализация с int / long. Вот как Java округляется.
Это, вероятно, самый эффективный метод, о котором вы могли подумать.
Если вы хотите сохранить двойное значение и использовать десятичную точность, то на самом деле это просто вопрос использования показателей 10 в зависимости от количества десятичных знаков.
Вы можете ввести отрицательный десятичный знак для десятичных точек, и это слово хорошо.
источник
Простой способ это:
источник
Этот пост содержит ответ, который вы ищете:
http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx
В основном это то, что он говорит:
Возвращаемое значение
Число ближайшего значения с точностью до цифр. Если значение находится посередине между двумя числами, одно из которых четное, а другое нечетное, возвращается четное число. Если точность значения меньше цифры, то значение возвращается без изменений.
Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира. Если цифры равны нулю, этот вид округления иногда называется округлением до нуля.
источник
Silverlight не поддерживает параметр MidpointRounding. Вот метод расширения для Silverlight, который добавляет перечисление MidpointRounding:
Источник: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/
источник
используя пользовательское округление
источник
>.5
производит то же поведение, что иMath.Round
. Вопрос в том, что происходит, когда десятичная часть точно0.5
. Math.Round позволяет указать желаемый алгоритм округленияЭто ужасно, как и весь ад, но всегда производит правильное арифметическое округление.
источник
Math.Round
самое можно сказать и о том, как вы хотите, чтобы оно было округлено.Вот способ, которым я должен был обойти это:
Попытка с 1,905 с двумя десятичными знаками даст 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