Почему команда >? 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
Ответ
Дело в том, что у 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.
Собственно, сам код преобразования (пара экранов) в основном посвящен этому округлению с последующим отрезанием незначащих нулей.
Комментариев нет:
Отправить комментарий