System.out.println(Math.round(2.5));
System.out.println(Math.round(3.5));
Получаем
3
4
Всё правильно? Да, так нас учили в школе, меня по-крайней мере. Советский союз - колыбель инженеров, а это стандартное математическое округление.
Но, теперь возьмем C#
Console.WriteLine(Math.Round(2.5));
Console.WriteLine(Math.Round(3.5));
Получаем
2
4
Оп-п-паньки. Неожиданно, правда? Получается что в C# округление работает не так как в Java. Лезем в документацию и читаем:
"Поведение этого метода соответствует стандарту IEEE-754, раздел 4. Этот способ округления иногда называют округлением до ближайшего или банковским округлением. Минимизирует ошибки округления, происходящие от постоянного округления среднего значения в одном направлении."
И ведь действительно, есть такой стандарт и действительно так принято. США – колыбель банкиров. И говорят, их так учат округлять в школе.
Но вернемся к нашему программированию. Чтобы сделать всё еще более запутанным, попробуем вот такой Java код
System.out.println(new DecimalFormat("0").format(2.5));
System.out.println(new DecimalFormat("0").format(3.5));
В результате
2
4
Вот такая вот петрушка, а теперь представьте что есть приложение где серверная часть написана на Java а клиентская на .Net и какие прелестные и неуловимые баги это может породить. Или пусть даже всё написано на Java, но например значения в таблице мы форматируем с помощью DecimalFormat а «итого» считаем с помощью Math.round(). Счастливых вам ночей с дебаггером, дорогие программисты…
К счастью, в .Net метод Round перегружен Round(Decimal) / Round(Decimal, MidpointRounding) и при необходимости можно явно указать способ округления. Таким образом, привести C# к Java достаточно просто:
Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero));
Console.WriteLine(Math.Round(3.5, MidpointRounding.AwayFromZero));
К сожалению, я не знаю простого способа привести Java к C#. Нужно либо использовать BigDecimal в конструктор которого передать MathContext с типом округления ROUND_HALF_EVEN либо писать собственный метод.
В g++ тоже есть аналогичный момент:
ReplyDelete#include
#include
int main ()
{
printf ("formatting of 2.5 is %.0f\n",2.5);
printf ("formatting of 3.5 is %.0f\n",3.5);
printf ("formatting of 2.51 is %.0f\n",2.51);
printf ("formatting of 3.51 is %.0f\n",3.51);
return 0;
}
$ g++ test.c
$ ./a.exe
formatting of 2.5 is 2
formatting of 3.5 is 3
formatting of 2.51 is 3
formatting of 3.51 is 4
т.е. мы привыкли, что 2.5 округляется в большую сторону, ан нет.
Этот способ округления иногда называют округлением до ближайшего или банковским округлением.
ReplyDeleteПо моему, правильно звучит как:
Этот способ округления иногда называют округлением до ближайшего четного или банковским округлением.
Если бы до ближайшего четного, то Round(2.99999) было бы 2
ReplyDeleteConsole.WriteLine(Math.Round(1099.485,2, MidpointRounding.AwayFromZero));
ReplyDeleteвыводит: 1099,48
а надо 1099,49!!!!!
и как это сделать?
В данном случае проблема возникает из-за двоичного представления чисел с плавающей точкой. Чтобы решить проблему Вам следует использовать decimal вместо double, вот так:
ReplyDeleteConsole.WriteLine(Math.Round(1099.485M, 2, MidpointRounding.AwayFromZero));
Я скажу по правде, вообще в математике не особо понимаю. В этих округлениях и обчислениях. Но, мне зарабатывать деньги на https://hashalot.io/blog/kak-dobyvat-efir-na-videokartah-s-4-gb-vram-v-2020-godu/ совсем не мешает.
ReplyDelete