Есть ли способ выполнить «правильное» арифметическое округление в .NET?/ С#

StackOverflow https://stackoverflow.com/questions/2514887

Вопрос

Я пытаюсь округлить число до первого десятичного знака, и, учитывая различные параметры 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:Я понятия не имею, как перечислить цифры без миллиона умножений и вычитаний.

Короче говоря:Как лучше всего это сделать?

Это было полезно?

Решение

Math.Round() ведет себя правильно.

Когда вы выполняете стандартное округление средней точки, вам никогда не придется заглядывать за пределы одной десятичной цифры за пределы места округления.Если вы округляете до ближайшей десятой, вам никогда не нужно смотреть дальше второй цифры после запятой.

Идея округления средней точки заключается в том, что половину промежуточных чисел следует округлять в большую сторону, а половину — в меньшую.Таким образом, для чисел от 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 должен округлите до 0,1, если хотите получить один десятичный знак.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top