Есть ли способ сделать «правильное» арифметическое округление в .NET? / C # - PullRequest
6 голосов
/ 25 марта 2010

Я пытаюсь округлить число до первого десятичного знака и, учитывая различные варианты округления в средней точке, кажется, что это хорошо работает. Однако возникает проблема, когда у этого числа есть последующие десятичные разряды, которые арифметически влияют на округление.

Пример:

С 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 ]

20 голосов
/ 25 марта 2010

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
15 голосов
/ 25 марта 2010

Я не понимаю, чего вы пытаетесь достичь здесь. 0,149 округляется до одного знака после запятой равно 0,1, а не 0,2

6 голосов
/ 25 марта 2010

Округление не является повторяющимся процессом, вы округляете только один раз.

То есть 0,146 округлено до 1 десятичной цифры равно 0,1.

Вы этого не делаете:

0.146 --> 0.15
0.15 -->  0.2

Вы делаете только это:

0.146 --> 0.1

В противном случае, следующее:

0.14444444444444446

также округляется до 0,2, но это не так, и не должно.

5 голосов
/ 25 марта 2010

Не пытайтесь сложить «ошибки» округления. Что ты и пытаешься сделать.

.146 следует округлить до .1, если вы идете с одним десятичным знаком.

Если сначала округлить до .15, а затем снова до .2, вы просто добавите больше ошибок округления, а не меньше.

...