web-dev-qa-db-fra.com

Arrondi des valeurs doubles en C #

Je veux une méthode d'arrondi sur les valeurs doubles en C #. Il doit pouvoir arrondir une valeur double à n'importe quelle valeur de précision d'arrondi. Mon code sous la main ressemble à:

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);
}

Donc RoundI (100, 3) devrait donner 99 et RoundI (1.2345, 0.001) devrait donner 1.235.

Le problème est que RoundI (1.275, 0.01) renvoie 1.27, plutôt que 1.28. En effet, lors de l'exécution de double valIntvRatio = val/intv, c'est-à-dire double valIntvRatio = 1,275/0,01, cela donne 0,122749999999999. Je sais que c'est un problème avec la double représentation dans n'importe quel langage de programmation. Ma question est, existe-t-il un code standard pour faire des choses comme ça, sans avoir à se soucier de la précision sur le double? Ici, j'ai défini la tolérance à 1e-14, mais cela est trop restrictif pour ce problème et je ne sais pas quelle est la tolérance correcte à définir. Merci pour toute aide.

30
Steve

Exemple d'utilisation de decimal, comme Kibbee l'a souligné

double d = 1.275;
Math.Round(d, 2);          // 1.27
Math.Round((decimal)d, 2); // 1.28 
42
Jimmy
double d = 1.2345;

Math.Round(d, 2);

le code ci-dessus devrait faire l'affaire.

5
AceMark

Si vous avez réellement besoin d'utiliser double remplacez-le simplement ci-dessous et cela fonctionnera mais avec les problèmes de précision habituels de l'arithmétique binaire à virgule flottante.

Il y a très certainement une meilleure façon de mettre en œuvre le "rounding" (presque une sorte d'arrondi des banquiers) que ma corde jonglant ci-dessous.

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;
}
2
Jonas Elfström

Les exemples utilisant la conversion décimale fournis dans la réponse de Jimmy ne répondent pas à la question, car ils ne montrent pas comment arrondir une valeur double à une valeur de précision d'arrondi comme demandé. Je crois que la bonne réponse en utilisant la conversion décimale est la suivante:

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

Parce qu'elle utilise le casting décimal, cette solution est sujette aux erreurs de casting mentionnées par Jeppe Stig Nielsen dans son commentaire à Jimmy's answer.

Notez également que j'ai spécifié MidpointRounding.AwayFromZero, car cela est conforme à la spécification du demandeur que RoundI (1.2345, 0.001) devrait donner 1.235.

2
R.T.