Есть ли способ сделать "правильное" арифметическое округление в.NET? / C#
Я пытаюсь округлить число до первого десятичного знака, и, учитывая различные варианты MidpointRounding, это, кажется, работает хорошо. Однако возникает проблема, когда у этого числа есть последующие десятичные разряды, которые арифметически влияют на округление.
Пример:
С 0.1
, 0.11..0.19
а также 0.141..0.44
оно работает:
Math.Round(0.1, 1) == 0.1
Math.Round(0.11, 1) == 0.1
Math.Round(0.14, 1) == 0.1
Math.Round(0.15, 1) == 0.2
Math.Round(0.141, 1) == 0.1
Но с 0.141..0.149
это всегда возвращается 0.1
, хотя 0.146..0.149
должен округлить до 0.2
:
Math.Round(0.145, 1, MidpointRounding.AwayFromZero) == 0.1
Math.Round(0.146, 1, MidpointRounding.AwayFromZero) == 0.1
Math.Round(0.146, 1, MidpointRounding.ToEven) == 0.1
Math.Round(0.146M, 1, MidpointRounding.ToEven) == 0.1M
Math.Round(0.146M, 1, MidpointRounding.AwayFromZero) == 0.1M
Я попытался придумать функцию, которая решает эту проблему, и она хорошо работает в этом случае, но, конечно, она гламурно проваливается, если вы пытаетесь округлить, т.е. 0.144449
это первая десятичная цифра (которая должна быть 0.2
, но результаты 0.1
.) (Это также не работает с Math.Round().)
private double "round"(double value, int digit)
{
// basically the old "add 0.5, then truncate to integer" trick
double fix = 0.5D/( Math.Pow(10D, digit+1) )*( value >= 0 ? 1D : -1D );
double fixedValue = value + fix;
// 'truncate to integer' - shift left, round, shift right
return Math.Round(fixedValue * Math.Pow(10D, digit)) / Math.Pow(10D, digit);
}
Я предполагаю, что решением будет перечислить все цифры, найти первое значение больше 4, а затем округлить в большую или другую сторону. Проблема 1: Это кажется идиотским, Проблема 2: я понятия не имею, как перечислить цифры без десятка умножений и вычитаний.
Короче говоря: каков наилучший способ сделать это?
4 ответа
Math.Round()
ведет себя правильно.
Когда вы выполняете стандартное округление средней точки, вам никогда не нужно смотреть за 1 десятичную цифру за пределы того места, куда вы округляете. Если вы округляете до ближайшей десятой, вам никогда не нужно смотреть за пределы второй цифры после десятичной точки.
Идея округления средней точки состоит в том, что половина промежуточных чисел должна округляться вверх, а половина - округляться. Поэтому для чисел от 0,1 до 0,2 половина из них должна округляться до 0,1, а половина - до 0,2. Средняя точка между этими двумя числами равна 0,15, так что это порог округления в большую сторону. 0,146 меньше 0,15, поэтому оно должно округляться до 0,1.
Midpoint
0.10 0.15 0.20
|----------------|----|---------------------|
0.146
<---- Rounds Down
Я не понимаю, что вы пытаетесь сделать здесь. 0,149 округляется до одного знака после запятой 0,1, а не 0,2
Округление не является повторяющимся процессом, вы округляете только один раз.
Таким образом, 0,146, округленное до 1 десятичного знака, равно 0,1.
Вы не делаете это:
0.146 --> 0.15
0.15 --> 0.2
Вы только делаете это:
0.146 --> 0.1
В противном случае, следующее:
0.14444444444444446
также округляется до 0,2, но это не так, и не должно.
Не пытайтесь сложить "ошибки" округления. Что ты и пытаешься сделать.
.146 должен округляться до.1, если вы идете с одним десятичным знаком.
Сначала округляя до.15, затем снова до.2, вы просто вносите больше ошибки округления, а не меньше.