Math round не работает

Малоизвестные детали реализации Math.Round() в .Net

Присказка

Если не любите детали, пролистывайте до следующего раздела,

Началось всё с того, что клиенты сообщили нам о расхождении в копейку в отчете. Значение 7.145 программа округлила до 7.14. Проверил на калькуляторе все вычисления, получил 7.145, обрадовался, что, как минимум, баг повторяется.

Первое подозрение пало на использование некорректных типов данных (float или double вместо decimal). Перепроверил ещё раз код, проблемы не нашёл. Создал новый юнит-тест на данную ситуацию, прогнал его, тест вполне ожидаемо упал. Потом запустил отладчик и стал смотреть все этапы вычислений (там довольно замороченная формула используется, значение округляется в самом конце, решил проверить, что было до округления). Перед округлением вижу вполне корректное значение 7.145. Глазам своим не верю, загоняю значение в watch-окно, проверяю ещё раз тип (Decimal, всё как положено), добавляю в watch-окне округление, получаю 7.14. Ущипнул себя на всякий случай и стал подставлять в Math.Round разные значения руками, проверять, что ещё может быть не так. Когда 7.155 вполне ожидаемо округлилось до 7.16, а 7.165 тоже до 7.16 начал подозревать, что это не я схожу с ума. Залез в MSDN и нашел…

Как реализовано округление в .Net

Math.Round поддерживает два режима округления:

  1. System.MidpointRounding.AwayFromZero — это привычный способ округления, +0.5 округляется до 1, а -0.5 округляется до -1. Т.е. в большую по модулю сторону.
  2. System.MidpointRounding.ToEven — округление «к четному», 0.5 округляется до 0, а 1.5 до 2.

Сам метод Math.Round имеет несколько перегруженных вариантов: для Double и для Decimal, с указанием количества знаков после запятой в результате и без, с указанием типа округления и без.

Самое интересное, что по умолчанию используется алгоритм округления System.MidpointRounding.ToEven. Про это в MSDN, конечно, написано, но лично я, например, считаю, что принцип «если ничего не помогает, прочтите, наконец, инструкцию» вполне логичен и следую ему по мере сил.

Покопавшись ещё в интернете, собрал информацию о том,

Для чего нужны несколько режимов округления

MSDN ограничивается замечанием «It conforms to IEEE Standard 754, section 4.» (Он соответствует стандарту IEEE 754, раздел 4.) Сам по себе стандарт посвящен вообще представлению дробных чисел в памяти компьютера и не так уж и много информации содержит о том, зачем нужны эти режимы округления. Более того, только текущая версия от 2008 года содержит упоминание о режиме округления «к четному», а вот предыдущая (от 1985 года) про него ещё была не в курсе. Википедия в статье про округление называет округление к четному «банковским» и рассказывает о том, какую проблему оно решает.

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

Как можно заметить, на 20 числах привычный нам режим округления дает погрешность в 1 (почти 5%). Лично для меня это ещё раз напомнило, что «самоочевидные выводы» обычно неверны — обыденным сознанием я всегда считал, что в 5 случаях округление происходит к 0 и в 5 случаях к 1, так что суммы исходной последовательности и округлённой должны сходиться. Это не так.

Ещё одну интересную историю про данный режим округления рассказала мне та самая клиентка, которая сообщила об этом баге. Раньше она долгое время работала учителем математики в школе и лет 30 назад застала момент, когда поменяли программу обучения и, в частности, преподавание правил округления. До того момента в школе давали округление «к четному», а потом всем стали давать более простую и менее правильную схему. Советская школа тогда была впереди сообщества американских инженеров.

Извлечение уроков

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

  • Функция ROUND в MSSQL умеет производить округление только в привычном режиме «от нуля».
  • Функция ROUND в MS Excel умеет производить округление только в привычном режиме «от нуля».
  • MS Excel имеет функцию ODD, которая округляет «к четному», но она округляет только до целых, точность указать нельзя.
  • Функция Math.Round в .Net по умолчанию внезапно округляет «к четному». Чуть менее, чем все программисты используют округление без указания режима, а чуть менее чем все клиенты при проверке продукта сверяют его с расчетами в Excel, где используют ROUND. Веселье с особо дотошными клиентами гарантировано.
  • Механизма глобального управления округлением (как настройками локали, форматом дат, например) в .Net отсутствует.
Читайте также:  Как настроить windows для работы со звуком

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

На сладкое

Чуть позже от тех же клиентов пришёл ещё багрепорт про округление.

Если посчитать на калькуляторе, то 1.58*25%=0.395, должно округляться до 0.40 в Excel, но этого не происходит. Если число ввести руками, то оно округляется верно. В данном случае, конечно же, ошибка возникает из-за того, что вычисление формул в Excel производится с использованием «неточных» типов данных. Если показать больше знаков, то картина получается более наглядной.

Всё вроде понятно и объяснимо, но доверие к Excel заметно уменьшилось. Получается, что инструмент, который то самое «обыденное сознание» воспринимало как источник эталонных данных, к которым должны сходиться результаты программы, больше не является таковым.

Мораль же всей этой истории такова:

Уважаемые коллеги, будьте внимательны и осторожны!

Источник

Math.Round, традиционное округление

Math.Round delaet 4etnoe okruglenie. Kak napisat’ funkciyu, delayuschoyu tradizionnoe okruglenie?

Math.Round(0.445,2) =====>>> 0.44
Math.Round(0.455,2) =====>>> 0.46

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Округление [Math.Round()]
Почему Math.Round(); при округлении выводит число с E? double a = Math.Round(0.00025, 6);.

Float и метод Math.Ceiling и Math.Round? как округлить float?
Согласно документации Microsoft метод Math.Ceiling и Math.Round не работает с float , а только с.

Math.Round
Пытаюсь округлить получаемое значение. результатов не видно. значение такое же private void.

Math.Round и ASP.NET
Добрый день уважаемые программисты. Почему в консольном приложении в следующем контексте .

Еще можно через формат:

Ya Format na samom dele uzhe pol’suyu. Vi dali pravil’nyj sovet.
Vot chto ya nashel:

Math.Round — ambigious invocation
собсна сабж. при вызове math.round выкидывает ошибку ambigious invocation. Math.Round(fractional.

Заменить Math.Min, Math.Max операторами if-else
Доброго времени. Как можно условия формул (Math.Min,Math.Max) заменить операторами if. Как в итоге.

Windows Form Math:: или Math
Добрый день! Подскажите пожалуйста как подключить библиотеку Math или написсать namespace что бы в.

Round() — округление
Всем доброго времени суток! У меня есть вопрос,касающиеся функции round(). У меня есть задание.

Источник

Math. Round Метод

Определение

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Округляет значение до ближайшего целого или указанного количества десятичных знаков.

Перегрузки

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

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

Округляет значение двойной точности с плавающей запятой до целого, используя указанное соглашение о округлении.

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

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

Округляет значение с плавающей запятой двойной точности до ближайшего целого значения; значения посередине округляются до ближайшего четного числа.

Округляет десятичное значение до ближайшего целого значения; значения посередине округляются до ближайшего четного числа.

Округляет десятичное значение до целого, используя заданное соглашение о округлении.

Примеры

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

Комментарии

Какой метод следует вызывать?

Для выбора соответствующего метода округления можно использовать следующую таблицу. Помимо Math.Round методов, он также включает Math.Ceiling и Math.Floor .

Кому Вызов
Округлить число до целого, используя соглашение о округлении до ближайшего числа. Round(Decimal)

Round(Double)

Округлить число до целого, используя заданное соглашение о округлении. Round(Decimal, MidpointRounding)

Round(Double, MidpointRounding)

Округлить число до указанного числа цифр дробной части с помощью соглашения о округлении до ближайшего значения. Round(Decimal, Int32)

Round(Double, Int32)

Округляет число до указанного числа цифр дробной части с использованием указанного соглашения о округлении. Round(Decimal, Int32, MidpointRounding)

Round(Double, Int32, MidpointRounding)

Округляет Single значение до указанного числа цифр дробной части, используя указанное соглашение о округлении и уменьшая потери точности. Преобразуйте в Single Decimal и вызов Round(Decimal, Int32, MidpointRounding) .
Округляет число до указанного числа цифр дробной части и сводит к минимуму проблемы с точностью округления значений средней точки. Вызовите метод округления, который реализует сравнение «больше или приблизительно равно». См. раздел округление и точность.
Округляйте дробное значение до целого, превышающее дробное значение. Например, округлить 3,1 до 4. Ceiling
Округляйте дробное значение до целого, меньшего, чем дробное значение. Например, округлить 3,9 до 3. Floor

Средние значения и соглашения о округлении

Округление предполагает преобразование числового значения с указанной точностью в значение с меньшей точностью. Например, можно использовать Round(Double) метод для округления значения 3,4 в 3,0, а Round(Double, Int32) метод для округления значения 3,579 до 3,58.

В средней величине значение после наименьшей значащих цифр в результате представляет собой ровно половину между двумя числами. Например, 3,47500 является средним значением, если необходимо округлить до двух десятичных разрядов, а 7,500 является средним значением, если необходимо округлить до целого числа. В таких случаях, если используется стратегия «цикл по ближайшей сторону», ближайшее значение не может быть легко идентифицировано без соглашения о округлении.

RoundМетод поддерживает два соглашения о округлении для обработки средних значений:

Округление от нуля

Средние значения округляются до следующего числа от нуля. Например, 3,75 округляется до 3,8, 3,85 округляет до 3,9,-3,75 округляются до-3,8, а-3,85 округляются до-3,9. Эта форма округления представлена MidpointRounding.AwayFromZero членом перечисления.

Округление до ближайшего четного или банковского округления

Средние значения округляются до ближайшего четного числа. Например, 3,75 и 3,85 округляются в 3,8, а обе-3,75 и-3,85 округляются в-3,8. Эта форма округления представлена MidpointRounding.ToEven членом перечисления.

В .NET Core 3,0 и более поздних версиях три дополнительные стратегии округления выделенные через MidpointRounding перечисление. Эти стратегии используются во всех случаях, а не только для средних значений, как MidpointRounding.ToEven и MidpointRounding.AwayFromZero .

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

В следующем примере показан сдвиг, который может быть получен от согласованного округления средних значений в одном направлении. В примере вычисляется истинное среднее для массива Decimal значений, а затем вычисляется среднее, когда значения в массиве округляются с использованием двух соглашений. В этом примере истинное значение и среднее значение, получаемое при округлении до ближайшего значения. Тем не менее, это означает, что результат округления от нуля отличается от 05 (или от 3,6%) от значения true.

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

Перегрузка Соглашение о округлении
Round(Decimal) ToEven
Round(Double) ToEven
Round(Decimal, Int32) ToEven
Round(Double, Int32) ToEven
Round(Decimal, MidpointRounding) Определяется mode параметром.
Round(Double, MidpointRounding) Определяется mode параметром
Round(Decimal, Int32, MidpointRounding) Определяется mode параметром
Round(Double, Int32, MidpointRounding) Определяется mode параметром

Округление и точность

Чтобы определить, включает ли операция округления среднюю величину, Round метод умножает исходное значение на округление до 10 n , где n — желаемое количество цифр дробной части в возвращаемом значении, а затем определяет, является ли оставшаяся часть значения больше или равна. 5. Это небольшая разновидность проверки на равенство, и, как обсуждалось в разделе «Проверка на равенство» Double справочного раздела, тесты на равенство с значениями с плавающей запятой являются проблематичными из-за проблем формата с плавающей запятой с двоичным представлением и точностью. Это означает, что любая часть числа, которая немного меньше 0,5 (из-за потери точности), не будет округляться вверх.

В следующем примере показана эта проблема. Он многократно добавляет от 0,1 до 11,0 и округляет результат до ближайшего целого числа. 11,5 должно округляться до 12 с использованием любого из соглашений по округлению ( ToEven или AwayFromZero ). Однако, как показано в выходных данных примера, это не так. В примере используется строка стандартного числового формата R для отображения полной точности значения с плавающей запятой и показывается, что округленное значение потеряет точность во время повторяющихся дополнений, и его значение фактически 11.499999999999998. Поскольку значение. 499999999999998 меньше 0,5, в игре не наблюдаются соглашения о средней округлении, и округление округляется вниз. Как показано в примере, эта проблема не возникает, если присвоить константе значение 11,5 Double переменной.

Проблемы, связанные с точностью округления значений, скорее всего, будут возникать в следующих случаях.

Если дробное значение не может быть точно выражено в двоичном формате типа с плавающей запятой.

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

Если округление имеет значение, Single а не Double или Decimal . Дополнительные сведения см. в следующем разделе: значения округления и одинарной точности с плавающей запятой.

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

Если операция округления вызывает перегрузку, которая округляет Double значение, можно изменить Double на Decimal значение и вызвать перегрузку, которая округляет Decimal значение. Хотя Decimal тип данных также имеет проблемы с представлением и потерей точности, эти проблемы гораздо менее распространены.

Определите пользовательский алгоритм округления, который выполняет тест «почти равно», чтобы определить, является ли округление приемлемым, близко к средней величине. В следующем примере определяется RoundApproximate метод, который проверяет, находится ли дробное значение рядом с средним значением, чтобы оно подлежит округлению средней степени. Как видно из выходных данных примера, он устраняет проблему округления, показанную в предыдущем примере.

Округление и одинарная точность значений с плавающей запятой

RoundМетод включает перегрузки, принимающие аргументы типа Decimal и Double . Нет методов, которые округляют значения типа Single . если передать Single значение в одну из перегрузок Round метода, оно будет приведено (в C#) или преобразовано (в Visual Basic) в Double , а также Round будет вызвана соответствующая перегрузка с Double параметром. Хотя это расширяющее преобразование, оно часто включает в себя потери точности, как показано в следующем примере. Если Single значение 16,325 передается в Round метод и округляется до двух десятичных разрядов с использованием округления до ближайшего значения, результат равен 16,33, а не ожидаемому результату 16,32.

Это непредвиденный результат из-за потери точности при преобразовании Single значения в Double . Так как итоговое Double значение 16.325000762939453 не является средним значением и больше 16,325, оно всегда округляется вверх.

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

Round(Double, Int32, MidpointRounding)

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

Параметры

Округляемое число двойной точности с плавающей запятой.

Количество дробных разрядов в возвращаемом значении.

Одно из значений перечисления, указывающее, какую стратегию округления использовать.

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

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

Исключения

Параметр digits имеет значение меньше 0 или больше 15.

mode не является допустимым значением MidpointRounding.

Комментарии

Значение digits аргумента может находиться в диапазоне от 0 до 15. Максимальное число целочисленных и дробных разрядов, поддерживаемое Double типом, равно 15.

Сведения о округлении чисел с средними значениями см. в разделе средние значения и соглашения о округлении .

При округлении средних значений алгоритм округления выполняет проверку равенства. Из-за проблем с двоичным представлением и точностью формата с плавающей запятой значение, возвращаемое этим методом, может быть непредвиденным. Дополнительные сведения см. в разделе округление и точность.

Если value аргумент имеет значение Double.NaN , метод возвращает Double.NaN . Если value параметр имеет значение Double.PositiveInfinity или Double.NegativeInfinity , то метод возвращает Double.PositiveInfinity или Double.NegativeInfinity соответственно.

Пример

В следующем примере показано, как использовать Round(Double, Int32, MidpointRounding) метод с MidpointRounding перечислением.

Примечания для тех, кто вызывает этот метод

Из-за потери точности, которая может быть результатом представления десятичных значений в виде чисел с плавающей запятой или выполнения арифметических операций с значениями с плавающей запятой, в некоторых случаях Round(Double, Int32, MidpointRounding) метод может не отображаться для округления среднего значения, как указано mode параметром. Это показано в следующем примере, где 2,135 округляется до 2,13 вместо 2,14. Это происходит из-за того, что внутренний метод умножается value на 10 цифр , а операция умножения в этом случае приводит к потере точности.

Источник

Оцените статью