Неочевидный момент в работе Math.Round(double, int)

Автор Тема: Неочевидный момент в работе Math.Round(double, int)  (Прочитано 8151 раз)

0 Пользователей и 4 Гостей просматривают эту тему.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
Всем привет!
Словил весьма неприятный для себя баг в своём приложении. Оказывается, я уже много лет использовал метод Math.Round(double, int) до конца не понимая, как он работает. Беглый поиск по нашему форуму показал, что, возможно, не я один такой. Так что, возможно, кому-то будет полезна эта информация.
Суть работы этого метода подробно и с примерами описана в статье: http://aakinshin.net/ru/blog/post/cheatsheet-rounding/
В чём же была проблема? Оказывается, по умолчанию, у этого метода весьма странное поведение при округлении числа, если значение первого отбрасываемого разряда равно 5. Округление выполняется (!тадам!) в сторону ближайшего чётного числа! То есть, как будто используется метод Math.Round (Double, Int32, MidpointRounding) со значением mode = ToEven. На примере: если мы имеем число 1.125 и будем округлять его до 2 знаков после запятой, то получим 1.12, а не 1.13, как ожидается по тем правилам округления, которые мы изучали в школе.
Однако, если использовать double.ToString с параметром формата, то округление выполняется по умолчанию с режимом AwayFromZero. Например: 1.125.ToString("#.##") = "1.13".

Оффлайн Захаров Максим

  • ADN OPEN
  • ***
  • Сообщений: 141
  • Карма: 3
Да есть такая штука. Я вот тоже уже давно хотел задать этот вопрос. И у меня было ещё как то так число 1.1256 если округлять до 2 знаков тоже давало 1.12
После этого я стал аккуратнее. Люди жалуются на разные результаты в civil одно а у меня на миллиметр другое. Но это не всегда.

Оффлайн Константин Виноградов

  • ADN OPEN
  • Сообщений: 12
  • Карма: 2
В python как и в других языках тоже есть данная особенность. А вот в C# есть возможность указать по умолчанию режим округления? Вот в python например можно в начале указать способ округления чисел и проблем дальше нет.
В начале программы задаётся опция например:
getcontext().rounding = ROUND_HALF_UP

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
А вот в C# есть возможность указать по умолчанию режим округления?
Так же глобально, как в Python, наверное, нет. По крайней мере, я такого способа не знаю. Мне пришлось везде, где использовал этот метод, заменять его на перегрузку Math.Round (Double, Int32, MidpointRounding) и указывать MidpointRounding = AwayFromZero.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13894
  • Карма: 1789
  • Рыцарь ObjectARX
  • Skype: rivilis
Мне пришлось везде, где использовал этот метод, заменять его на перегрузку Math.Round (Double, Int32, MidpointRounding) и указывать MidpointRounding = AwayFromZero.
Или можно было создать свой метод Math.RoundTrue(Double, Int32), который бы вызывал Math.Round (Double, Int32, MidpointRounding.AwayFromZero)
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
Я думал об этом, но я решил, что не так уж сложно третий параметр дописывать.

Оффлайн Константин Виноградов

  • ADN OPEN
  • Сообщений: 12
  • Карма: 2
Тут бы через регулярное выражение автозамену сделать с дописыванием 3го параметра.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
http://bash.im/quote/401148 :)
Серьёзно, проще ручками быстро пройтись по решению.

Оффлайн Константин Виноградов

  • ADN OPEN
  • Сообщений: 12
  • Карма: 2
Согласен, когда объем мал то ручками проще. И анекдот в тему.  :)

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
Ну, относительно мал:
Find all "Round", Match case, Whole word, Find Results 1, Entire Solution, ""
...
Matching lines: 35    Matching files: 19    Total files searched: 791
Т.е., 35 использований метода в 19 файлах. Но лично мне всё равно проще ручками пройтись, чем составлять регулярку, которая будет учитывать все возможные варианты, встречающиеся в коде. Надёжнее и спокойней.

Оффлайн Алексей Терно

  • ADN Club
  • ****
  • Сообщений: 382
  • Карма: 33
    • C3D Extensions
  • Skype: alexeyterno
если значение первого отбрасываемого разряда равно 5. Округление выполняется (!тадам!) в сторону ближайшего чётного числа
Это не баг - это такой метод округления. У нас им трассовики пользуются.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 738
Да я не говорю, что это баг метода. Просто поведение оказалось не то, которое ожидаешь по умолчанию. И, в результате, появился баг моего приложения. К примеру, пользователь видит в свойствах объекта значение 1.71, а ему надо 1.7. Он исправляет на 1.7, но вместо этого получает 1.69. Он снова прописывает 1.7, объект изменяется и в свойствах выскакивает 1.71. Приходилось вбивать что-то типа 1.695, тогда, в итоге, получалось нужное значение 1.7. А всё потому, что в вычислениях составляющих этого свойства использовался этот метод округления и "звёзды сложились неудачно". Хотел скринкастом показать, но что-то он глючит сегодня.
Вот как это выглядело:
« Последнее редактирование: 26-03-2018, 17:57:58 от Дмитрий Загорулькин »