2010-01-25 12 views
31

Je veux une méthode d'arrondi sur les valeurs doubles en C#. Il doit être capable d'arrondir une double valeur à toute valeur de précision d'arrondi. Mon code à la main ressemble:Arrondir les valeurs doubles en 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); 
} 

Ainsi 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 au lieu de 1.28. C'est parce que lors de l'exécution double valIntvRatio = val/intv, c'est-à-dire double valIntvRatio = 1.275/0.01, il donne 0.12749999999999. Je sais que c'est un problème avec la double représentation dans n'importe quel langage de programmation. Ma question est, y a-t-il un code standard pour faire des choses comme ça, sans avoir à se soucier de la précision sur le double? Ici, je mets le tolérant à 1e-14, mais c'est trop restrictif pour ce problème et je ne sais pas quelle est la tolérance correcte à régler. Merci pour toute aide.

+5

Vous devriez peut-être envisager d'utiliser le type de données Decimal. – Kibbee

+0

Pourquoi arrondir (100,3) donner 99? Si vous arrondissez à la même position fractionnaire que les 3 (0 places), vous obtiendrez 100, pas 99. – paxdiablo

+0

paxdiablo: désolé, le but de RoundI est de ne pas arrondir pour arrondir le premier paramètre à la même position fractionnaire comme le deuxième paramètre. Le deuxième paramètre est l'intervalle rond et l'arrondi arrondit le premier paramètre à la valeur de placard qui a le mode 0 au deuxième paramètre. – Steve

Répondre

41

Exemple d'utilisation decimal, comme Kibbee a souligné

double d = 1.275; 
Math.Round(d, 2);   // 1.27 
Math.Round((decimal)d, 2); // 1.28 
+1

Bien que cette grande solution fonctionne généralement, lorsque les chiffres significatifs de 'd' sont proches de la limitation de la précision d'un' double ', la conversion en 'decimal' enlève en fait trop de précision. A titre d'exemple, prenez 'd = 123456789.256;' (vous devez sortir ceci avec 'd.ToString (" R ")' pour révéler une précision "cachée"). Si vous utilisez simplement 'Math.Round (d, 2)' dans cet exemple (n'oubliez pas d'écrire le résultat avec '" R "') vous obtiendrez un meilleur résultat que si vous faites 'Math.Round ((decimal) d, 2) '.Cette conversion en 'decimal' enlève trop de précision ici. Dans ce cas, utilisez 'decimal' depuis le début, pas de conversion. –

+0

+1 très belle solution, mais malheureusement elle ne tourne pas ** de 0,005 ** à ** 0,01 **. Le résultat est ** 0 ** –

+8

@Zefnus car il utilise l'arrondi du banquier. Si vous voulez arrondir à 0.01, utilisez 'Math.Round ((décimal) d, 2, MidpointRounding.AwayFromZero)' – Jimmy

5
double d = 1.2345; 

Math.Round(d, 2); 

le code ci-dessus devrait faire l'affaire.

2

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

Il y a certainement une meilleure façon de mettre en œuvre le "arrondi" (presque une sorte d'arrondi des banquiers) que ma jonglerie de cordes 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; 
} 
+0

génial ... Fanks ... m'a sauvé un peu de temps à travailler celui-là. – Jon

+0

peut-être trouvé un bug. Si vous passez: number = 0.5 intervalle d'arrondi 1.0 les choses vont un peu mal avec le bit de tableau var temp. – Jon

+0

C'est vrai, mais un plus grand intervalle que le nombre n'a aucun sens, n'est-ce pas? J'ai ajouté un contrôle supplémentaire dans la méthode. –

2

Les exemples en utilisant la coulée dans décimales fourni la réponse de Jimmy faire, car ils ne montrent pas répondre pas à la question comment arrondir une valeur double à une valeur de précision arrondi tel que demandé. Je crois que la réponse correcte à l'aide coulée 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'il utilise la coulée décimale, cette solution est soumise aux erreurs de coulée mentionnées par Jeppe Stig Nielsen dans son commentaire à la réponse de Jimmy .

Notez également que j'ai spécifié MidpointRounding.AwayFromZero, car cela est cohérent avec la spécification du demandeur que RoundI (1,2345, 0,001) devrait donner 1,235.