Почему команда >? 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
Почему команда >? 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.
Собственно, сам код преобразования (пара экранов) в основном посвящен этому округлению с последующим отрезанием незначащих нулей.
0.449999988079071044921875? Это не midpoint, это строго меньше середины.
– VladD
Jul 13 '16 at 17:22
decimal произошло округление до в точности 3.45.
– VladD
Jul 14 '16 at 09:47
double и пользоваться исключительно decimal.
– VladD
Jul 15 '16 at 06:56
Из https://stackoverflow.com/a/8240013/5574962 Для того чтобы обойти проблему можно применять такой подход. Такие вычисления, конечно, медленнее.
Math.Round((Decimal)0.45f, 1, MidpointRounding.AwayFromZero)
Возвращает 0.5
0.45f != 0.45dи имеют отклонения в разные стороны от0.45от того и разница. – Дмитрий Чистик Jul 12 '16 at 06:19