Является ли С# десятичным округлением несогласованным?
Я сражался с десятичной точностью в С#, исходящей из SQL Decimal (38,30), и я, наконец, сделал все возможное для округления странности. Я знаю, что я, вероятно, не замечаю здесь очевидного, но мне нужно немного понять.
Проблема, с которой я сталкиваюсь, заключается в том, что С# не создает то, что я считаю совместимым.
decimal a = 0.387518769125m;
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(a, 11));
Console.WriteLine(Math.Round(b, 11));
Console.WriteLine(Math.Round(a, 11) == Math.Round(b, 11));
Урожайность
0.38751876912
0.38751876913
False
Uhh, 0.38751876913? В самом деле? Что мне здесь не хватает?
От MSDN:
Если цифра в позиции десятичного разряда нечетна, она изменяется на четную цифру. В противном случае он остается неизменным.
Почему я вижу противоречивые результаты? Дополнительная точность не меняет "цифру в десятичном положении"...
Ответы
Ответ 1
Из MSDN:
Если в d
есть нулевая цифра вправо десятичной позиции decimals
, а ее значение 5
, цифра в десятичной запятой округляется, если она нечетная, или остается неизменной, если она даже. Если d
имеет меньше дробных цифр, чем decimals
, d
возвращается без изменений.
В вашем первом случае
decimal a = 0.387518769125m;
Console.WriteLine(Math.Round(a, 11));
там - одна цифра справа 11-го места, и это число 5
. Поэтому , поскольку позиция 11 четная, она остается неизменной. Таким образом, вы получаете
0.38751876912
В вашем втором случае
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(b, 11));
там нет ни одной цифры справа 11-го места. Поэтому это прямое округление округления; вы округлите, если следующая цифра больше 4, иначе вы округлите вниз. Поскольку цифра справа от 11-го места больше 4 (это 5), мы округливаем, чтобы вы видели
0.38751876913
Почему я вижу непоследовательные результаты?
Ты не. Результаты полностью соответствуют документации.
Ответ 2
Из MSDN - Метод Math.Round(десятичный, Int32):
Если есть одна ненулевая цифра в d справа от десятичной позиции десятичного знака, а ее значение равно 5, цифра в десятичной запятой округляется, если она нечетная, или остается неизменной, если она четная. Если d имеет меньше дробных цифр, чем десятичные числа, d возвращается без изменений.
Поведение этого метода следует за стандартом IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округления банкира. Он минимизирует ошибки округления, возникающие из-за постоянного округления среднего значения в одном направлении.
Обратите внимание на использование одиночной ненулевой цифры. Это соответствует вашим первым примерам, но не второй.
и
Чтобы управлять типом округления, используемым методом Round (Decimal, Int32), вызовите перегрузку Decimal.Round(десятичное, Int32, MidpointRounding).
Ответ 3
Часть "одиночная ненулевая цифра в d справа от десятичной позиции десятичного знака и ее значение равна 5" объясняет результат. Только тогда, когда часть раунда равна 0,5, правило округления входит в игру.
Ответ 4
Сдвиньте оба числа на 11 цифр влево:
+38751876912,5
+38751876912,50002636113061835
Используя округление банкира, мы обходим первый. Под каждой системой округления окружности мы округляем второе число (потому что оно не находится в средней точке).
.Net делает именно то, что мы ожидаем от него.