Округление двойных значений в C # - PullRequest
30 голосов
/ 25 января 2010

Я хочу метод округления на двойные значения в C #. Он должен иметь возможность округлять двойное значение до любого значения точности округления. Мой код на руках выглядит так:

public static double RoundI(double number, double roundingInterval) {

    if (roundingInterval == 0.0)
    {
        return;
    }

    double intv = Math.Abs(roundingInterval);
    double sign = Math.Sign(number);
    double val = Math.Abs(number);

    double valIntvRatio = val / intv;
    double k = Math.Floor(valIntvRatio);
    double m = valIntvRatio - k;

    bool mGreaterThanMidPoint = ((m - 0.5) >= 1e-14) ? true : false;
    bool mInMidpoint = (Math.Abs(m - 0.5) < 1e-14) ? true : false;
    return (mGreaterThanMidPoint || mInMidpoint) ? sign * ((k + 1) * intv) : sign * (k * intv);
}

Таким образом, RoundI (100, 3) должен дать 99, а RoundI (1.2345, 0.001) должен дать 1.235.

Проблема в том, что RoundI (1,275, 0,01) возвращает 1,27, а не 1,28. Это связано с тем, что при выполнении double valIntvRatio = val / intv, то есть double valIntvRatio = 1.275 / 0.01, он дает 0.12749999999999. Я знаю, что это проблема с двойным представлением на любом языке программирования. У меня вопрос, есть ли стандартный код для таких вещей, без необходимости беспокоиться о точности в два раза? Здесь я установил допуск на 1e-14, но это слишком ограниченно для этой проблемы, и я не знаю, какой правильный допуск нужно установить. Спасибо за любую помощь.

Ответы [ 4 ]

42 голосов
/ 25 января 2010

Пример использования decimal, как Kibbee указал

double d = 1.275;
Math.Round(d, 2);          // 1.27
Math.Round((decimal)d, 2); // 1.28 
5 голосов
/ 25 января 2010
double d = 1.2345;

Math.Round(d, 2);

приведенный выше код должен помочь.

2 голосов
/ 03 февраля 2016

Примеры использования десятичного приведения, представленные в ответе Джимми , не отвечают на вопрос, так как они не показывают, как округлить двойное значение до любого значения точности округления по запросу. Я считаю, что правильный ответ с использованием десятичного приведения следующий:

    public static double RoundI(double number, double roundingInterval)
    {
        return (double)((decimal)roundingInterval * Math.Round((decimal)number / (decimal)roundingInterval, MidpointRounding.AwayFromZero));
    }

Поскольку используется десятичное приведение, это решение подвержено ошибкам приведения, упомянутым Джеппе Стигом Нильсеном в его комментарии к ответу Джимми .

Кроме того, обратите внимание, что я указал MidpointRounding.AwayFromZero, поскольку это согласуется со спецификацией запрашивающего, что RoundI (1.2345, 0.001) должен давать 1.235.

2 голосов
/ 26 января 2010

Если вам действительно нужно использовать double, просто замените его ниже, и оно будет работать, но с обычными проблемами точности двоичной арифметики с плавающей точкой.

Скорее всего, есть лучший способ реализовать "округление" (почти своего рода банковское округление), чем мое жонглирование строк ниже.

public static decimal RoundI(decimal number, decimal roundingInterval)
{
   if (roundingInterval == 0) { return 0;}

   decimal intv = Math.Abs(roundingInterval);
   decimal modulo = number % intv;
   if ((intv - modulo) == modulo) {
       var temp = (number - modulo).ToString("#.##################");
       if (temp.Length != 0 && temp[temp.Length - 1] % 2 == 0) modulo *= -1;
   }
    else if ((intv - modulo) < modulo)
        modulo = (intv - modulo);
    else
        modulo *= -1;

    return number + modulo;
}
...