Округление до четного 131,575 дает четное число вместо четного - PullRequest
0 голосов
/ 29 августа 2018

Рассмотрим следующий пример кода:

var tests = new List<double> { 131.505, 131.515, 131.525, 131.535, 131.545, 131.555, 131.565, 131.575, 131.585, 131.595 };
foreach (double n in tests)
{
    Console.WriteLine("{0} => {1}", n, Math.Round(n, 2, MidpointRounding.ToEven));
}

И его вывод :

131.505 => 131.5
131.515 => 131.51 <- wt*
131.525 => 131.52
131.535 => 131.54
131.545 => 131.54
131.555 => 131.56
131.565 => 131.56
131.575 => 131.57 <- wt*
131.585 => 131.58
131.595 => 131.6

Я ожидал :

131.515 => 131.52
131.575 => 131.58

Почему алгоритм MidpointRounding.ToEven выдает число с нечетным числом в конце; и могу ли я что-нибудь сделать, чтобы это исправить?

Справочная информация: я передаю те же цифры в функцию PHP round($n, 2, PHP_ROUND_HALF_EVEN). Цель состоит в том, чтобы оба сценария дали одинаковые результаты.

Я был бы признателен за объяснение того, что происходит за кулисами в этом конкретном примере, вместо стандартного ответа "потому что математика с плавающей запятой сломана". Я хотел бы знать, почему PHP может дать ожидаемые результаты, а .NET нет? Я хотел бы знать, если с плавающей точкой .NET сломана вместо самой с плавающей точкой.

1 Ответ

0 голосов
/ 29 августа 2018

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

В показанном вами коде исходный текст 131.515 преобразуется из десятичного числа в значение double. Поскольку 131.515 нельзя представить точно в double, создается ближайшее представимое значение. Это 131,5149999999999863575794734060764312744140625.

Таким образом, когда вызывается Math.round, ему присваивается значение 131,5149999999999863575794734060764312744140625. Поскольку это значение меньше 131,515, оно округляется до 131,51.

Как отметил Марк Дикинсон в комментарии, Math.Round сама по себе является несовершенной реализацией и содержит внутренние ошибки округления. Для исходного текста 131.525 преобразование в double дает 131,525000000000005684341886080801486968994140625. Округление этого до двух десятичных цифр после десятичной запятой должно дать 131,53. Однако Math.Round, по-видимому, вычисляет результат, сначала умножая на 100. Поскольку математический результат умножения на 100 не является точно представимым, он округляется до ближайшего представимого значения, которое равно 13152,5. Затем округление этого числа до целого числа с помощью правила округления до ближайших связей до четных значений дает 13152. Затем его деление и преобразование в десятичное число приводит к получению «131,52».

Таким образом, мы не можем ожидать, что Math.round даст правильные результаты.

...