Страницы

Поиск по вопросам

пятница, 13 декабря 2019 г.

Округление 0,45 до десятых

#c_sharp #net #математика


Почему команда >? Math.Round(0.45f, 1, MidpointRounding.AwayFromZero) возвращает
0.4 , а команда >? Math.Round(0.45d, 1, MidpointRounding.AwayFromZero) 0.5 ? 

Команды выполнены в окне команд Visual Studio 2008, версия .Net Framework 3.5
    


Ответы

Ответ 1



Дело в том, что у 0.45 нет точного представления в двоичном виде. Это приводит к потерям точности даже там, где разработчик ожидает более точного результата. В вашем случае происходит upcast float-значения 0.45 к double. 0.45f выглядит примерно так 0 01111101 1100 11001100 11001100 110 Это примерно 0.449999988079071044921875. .NET знает что точность float недостаточна, и при преобразовании в строку использует 7 значащих цифр. 0.4499999 88 отображается как 0.45. Поэтому и в отладке, и при выводе на консоль вы увидите 0.45. 0.45d - представляется примерно так: 0 0111111 1101 1100 11001100 11001100 11001100 11001100 11001100 11001101 Что примерно равно 0.450000000000000011102230246252. Для double при преобразовании в строку .NET использует 14 значащих цифр. Значение представляется как 0.45000000000000 00 -> 0.45 Ок, теперь вы даете процессору 0.45f и просите его сконвертировать его в double. Процессор берет и добивает нулями: 0 0111111 1101 1100 11001100 11001100 11000000 00000000 00000000 00000000 Что все еще равно 4.49999988079071044921875 С точки зрения процессора - точность не пострадала. Но теперь это double, и при операциях с ним предполагается что он достаточно точен, чтобы при работе с ним как с десятичным числом захватить побольше цифр. Это больше не "примерно 0.45". Это ведь double. Теперь в нем примерно 14 значащих цифр - значение с точки зрения .NET и процессора превратилось в "примерно 0.449999988079071". Т.е. в двоичном представлении точность не потерялась, в десятичном - тоже - но полученное число теперь отличается от 0.45d, что приводит к округлению в неожиданном направлении. Почему работает аналогичный вызов с приведением к decimal: Math.Round((Decimal)0.45f, 1, MidpointRounding.AwayFromZero) // 0.5 Приведение в decimal происходит через системный вызов метода VarDecFromR4, который при конвертации округляет float до 7 значащих десятичных цифр, делая из 0.449999988079071044921875 красивое 0.4500000: // Round the input to a 7-digit integer. The R4 format has // only 7 digits of precision, and we want to keep garbage digits // out of the Decimal were making. Собственно, сам код преобразования (пара экранов) в основном посвящен этому округлению с последующим отрезанием незначащих нулей.

Ответ 2



Из https://stackoverflow.com/a/8240013/5574962 Для того чтобы обойти проблему можно применять такой подход. Такие вычисления, конечно, медленнее. Math.Round((Decimal)0.45f, 1, MidpointRounding.AwayFromZero) Возвращает 0.5

Комментариев нет:

Отправить комментарий