Pourquoi Math.Round (2.5) renvoie 2 au lieu de 3?

Dans C #, le résultat de Math.Round(2.5) est 2.

C’est censé être 3, n’est-ce pas? Pourquoi est-ce 2 plutôt en C #?

Tout d’abord, ce ne serait pas un bug C # de toute façon – ce serait un bug .NET. C # est le langage – il ne détermine pas comment Math.Round est implémenté.

Et deuxièmement, non – si vous lisez les documents , vous verrez que l’arrondi par défaut est “arrondi à même” (l’arrondi du banquier):

Valeur de retour
Type: System.Double
L’entier le plus proche a. Si la composante fractionnaire de a est à mi-chemin entre deux nombres entiers, l’un étant pair et l’autre impair, le nombre pair est renvoyé. Notez que cette méthode renvoie un type Double au lieu d’un type intégral.

Remarques
Le comportement de cette méthode est conforme à la norme IEEE 754, section 4. Ce type d’arrondi est parfois appelé arrondi au plus proche ou arrondi par le banquier. Cela minimise les erreurs d’arrondi qui résultent de l’arrondissement constant d’une valeur de point milieu dans une seule direction.

Vous pouvez spécifier comment Math.Round doit arrondir les points intermédiaires en utilisant une surcharge qui prend une valeur MidpointRounding . Il y a une surcharge avec un MidpointRounding correspondant à chacune des surcharges qui n’en a pas:

  • Round(Decimal) / Round(Decimal, MidpointRounding)
  • Round(Double) / Round(Double, MidpointRounding)
  • Round(Decimal, Int32) / Round(Decimal, Int32, MidpointRounding)
  • Round(Double, Int32) / Round(Double, Int32, MidpointRounding)

Le choix de cette valeur par défaut est différent. ( MidpointRounding n’a été introduit que dans .NET 2.0. Avant, je ne suis pas sûr qu’il y ait un moyen facile d’implémenter le comportement souhaité sans le faire vous-même.) En particulier, l’historique a montré que ce n’est pas le comportement attendu . c’est un péché cardinal dans la conception de l’API. Je peux voir pourquoi l’arrondi de Banker est utile … mais c’est toujours une surprise pour beaucoup.

Vous serez peut-être intéressé par l’énumération équivalente Java la plus proche ( RoundingMode ) qui offre encore plus d’options. (Il ne s’agit pas seulement des points médians.)

C’est ce qu’on appelle l’arrondi à l’arrondi (ou l’arrondi du banquier), qui est une stratégie d’arrondissement valide pour minimiser les erreurs accumulées dans les sums (MidpointRounding.ToEven) . La théorie est la suivante: si vous arrondissez toujours un nombre de 0,5 dans la même direction, les erreurs s’accumuleront plus rapidement (le retour à la normale est supposé minimiser cela) (a) .

Suivez ces liens pour les descriptions MSDN de:

  • Math.Floor , qui se Math.Floor par l’infini négatif.
  • Math.Ceiling , qui arrondit vers l’infini positif.
  • Math.Truncate , qui arrondit vers le haut ou le bas vers zéro.
  • Math.Round , qui arrondit à l’entier le plus proche ou au nombre spécifié de décimales. Vous pouvez spécifier le comportement s’il est exactement équidistant entre deux possibilités, tel que l’arrondi pour que le dernier chiffre soit pair (” Round(2.5,MidpointRounding.ToEven) ” devenant 2) ou pour qu’il soit plus éloigné de zéro (” Round(2.5,MidpointRounding.AwayFromZero) “devenant 3).

Le diagramme et le tableau suivants peuvent vous aider:

 -3 -2 -1 0 1 2 3 +--|------+---------+----|----+--|------+----|----+-------|-+ abcde a=-2.7 b=-0.5 c=0.3 d=1.5 e=2.8 ====== ====== ===== ===== ===== Floor -3 -1 0 1 2 Ceiling -2 0 1 2 3 Truncate -2 0 0 1 2 Round(ToEven) -3 0 0 2 3 Round(AwayFromZero) -3 -1 0 2 3 

Notez que Round est beaucoup plus puissant qu’il n’y paraît, simplement parce qu’il peut arrondir à un nombre spécifique de décimales. Tous les autres arrondissent à zéro les décimales toujours. Par exemple:

 n = 3.145; a = System.Math.Round (n, 2, MidpointRounding.ToEven); // 3.14 b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15 

Avec les autres fonctions, vous devez utiliser la technique de multiplication / division pour obtenir le même effet:

 c = System.Math.Truncate (n * 100) / 100; // 3.14 d = System.Math.Ceiling (n * 100) / 100; // 3.15 

(a) Bien sûr, cette théorie dépend du fait que vos données ont une répartition assez égale des valeurs entre les moitiés paires (0,5, 2,5, 4,5, …) et les moitiés impaires (1,5, 3,5, …).

Si toutes les “demi-valeurs” sont égales (par exemple), les erreurs s’accumulent aussi vite que si vous aviez toujours arrondi.

De MSDN, Math.Round (double a) renvoie:

L’entier le plus proche a. Si la composante fractionnaire de a est à mi-chemin entre deux nombres entiers, l’un étant pair et l’autre impair, le nombre pair est renvoyé.

… et donc 2,5, étant à mi-chemin entre 2 et 3, est arrondi au nombre pair (2). cela s’appelle l’arrondissement des banquiers (ou round-to-even), et est une norme d’arrondissement couramment utilisée.

Même article MSDN:

Le comportement de cette méthode est conforme à la norme IEEE 754, section 4. Ce type d’arrondi est parfois appelé arrondi au plus proche ou arrondi par le banquier. Cela minimise les erreurs d’arrondi qui résultent de l’arrondissement constant d’une valeur de point milieu dans une seule direction.

Vous pouvez spécifier un comportement d’arrondi différent en appelant les surcharges de Math.Round qui prennent un mode MidpointRounding .

Vous devriez vérifier MSDN pour Math.Round :

Le comportement de cette méthode est conforme à la norme IEEE 754, section 4. Ce type d’arrondi est parfois appelé arrondi au plus proche ou arrondi par le banquier.

Vous pouvez spécifier le comportement de Math.Round utilisant une surcharge:

 Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3 Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2 

La nature de l’arrondi

Considérons la tâche d’arrondir un nombre qui contient une fraction pour, par exemple, un nombre entier. Le processus d’arrondi dans ce cas consiste à déterminer quel nombre entier représente le mieux le nombre que vous avez arrondi.

En général, ou l’arrondissement «arithmétique», il est clair que 2.1, 2.2, 2.3 et 2.4 arrondissent à 2.0; et 2.6, 2.7, 2.8 et 2.9 à 3.0.

Cela laisse 2,5, ce qui n’est pas plus proche de 2.0 que de 3.0. C’est à vous de choisir entre 2.0 et 3.0, ou bien serait tout aussi valable.

Pour les nombres inférieurs, -2,1, -2,2, -2,3 et -2,4 deviendraient -2,0; et -2,6, 2,7, 2,8 et 2,9 deviendraient -3,0 sous l’arrondissement arithmétique.

Pour -2.5, un choix est nécessaire entre -2,0 et -3,0.

Autres formes d’arrondi

“Arrondir” prend n’importe quel nombre avec des décimales et en fait le nombre “entier” suivant. Ainsi, non seulement faire 2.5 et 2.6 arrondir à 3.0, mais aussi 2.1 et 2.2.

Arrondir les nombres positifs et négatifs éloigne de zéro. Par exemple. 2,5 à 3,0 et -2,5 à -3,0.

“Arrondir” tronque les nombres en coupant les chiffres indésirables. Cela a pour effet de déplacer les nombres vers zéro. Par exemple. 2,5 à 2,0 et -2,5 à -2,0

Dans “l’arrondi du banquier” – dans sa forme la plus courante -, le 0,5 à arrondir est arrondi à la hausse ou à la baisse pour que le résultat de l’arrondi soit toujours un nombre pair. Ainsi, 2,5 arrondit à 2,0, 3,5 à 4,0, 4,5 à 4,0, 5,5 à 6,0, etc.

“Arrondi alternatif” alterne le processus pour tout .5 entre arrondis et arrondis.

“Arrondi au hasard” arrondit un 0,5 vers le haut ou le bas sur une base entièrement aléatoire.

Symésortinge et asymésortinge

Une fonction d’arrondi est dite «symésortingque» si elle arrondit tous les nombres à zéro ou arrondit tous les nombres à zéro.

Une fonction est «asymésortingque» si elle arrondit les nombres positifs vers zéro et les nombres négatifs loin de zéro. 2,5 à 2,0; et -2,5 à -3,0.

Aussi asymésortingque est une fonction qui arrondit les nombres positifs de zéro et les nombres négatifs vers zéro. Par exemple. 2,5 à 3,0; et -2,5 à -2,0.

La plupart du temps, les gens pensent à un arrondi symésortingque, où -2,5 sera arrondi à -3,0 et 3,5 sera arrondi à 4,0. (en C # Round(AwayFromZero) )

L’ MidpointRounding.ToEven par défaut MidpointRounding.ToEven ou Bankers ( 2,5 devenant 2, 4,5 devient 4 et ainsi de suite ) m’a déjà frappé avec la rédaction de rapports pour la comptabilité, donc je vais écrire quelques mots de ce que j’ai découvert précédemment et de dans ce pour ce poste.

Qui sont ces banquiers qui arrondissent les chiffres pairs (les banquiers britanniques peut-être!)?

De wikipedia

L’origine du terme arrondi des banquiers rest plus obscure. Si cette méthode d’arrondissement était une norme dans le secteur bancaire, les preuves se sont révélées extrêmement difficiles à trouver. Au contraire, la section 2 du rapport de la Commission européenne intitulé L’introduction de l’euro et l’arrondi des montants en devises suggère qu’il n’y avait pas eu auparavant d’approche standard pour arrondir les opérations bancaires; et il spécifie que les montants “à mi-chemin” devraient être arrondis.

Cela semble être une façon très étrange d’arrondir en particulier pour les banques, à moins bien sûr que les banques n’utilisent beaucoup de repositorys de même montant. Déposez 2,4 M £, mais nous l’appellerons Monsieur 2 M £.

La norme IEEE 754 date de 1985 et donne les deux manières d’arrondir, mais avec le banquier comme recommandé par la norme. Cet article de wikipedia a une longue liste de la façon dont les langues mettent en œuvre l’arrondi (corrigez-moi si l’un des éléments ci-dessous est faux) et la plupart n’utilisent pas les banquiers, mais l’arrondissement que vous apprenez à l’école:

  • C / C ++ round () à partir de math.h arrondit à zéro (pas d’arrondi banquier)
  • Java Math.Round arrondit à zéro (le résultat est étagé , 0,5 est ajouté, et un entier est généré ). Il y a une alternative dans BigDecimal
  • Perl utilise une méthode similaire à C
  • Le Javascript est le même que celui de Java.

De MSDN:

Par défaut, Math.Round utilise MidpointRounding.ToEven. La plupart des gens ne sont pas habitués à «arrondir à même», l’alternative «se détacher de zéro» étant plus communément enseignée à l’école. .NET par défaut à “Arrondir à même” car il est statistiquement supérieur car il ne partage pas la tendance de “arrondir à zéro” pour arrondir un peu plus souvent qu’il ne l’est (en supposant que les nombres arrondis ont tendance à être positifs. )

http://msdn.microsoft.com/en-us/library/system.math.round.aspx

Étant donné que Silverlight ne prend pas en charge l’option MidpointRounding, vous devez écrire le vôtre. Quelque chose comme:

 public double RoundCorrect(double d, int decimals) { double multiplier = Math.Pow(10, decimals); if (d < 0) multiplier *= -1; return Math.Floor((d * multiplier) + 0.5) / multiplier; } 

Pour les exemples incluant comment l'utiliser comme extension, voir le post: .NET et Silverlight Rounding

J’ai eu ce problème où mon serveur SQL arrondit 0,5 à 1 alors que mon application C # ne l’a pas fait. Donc, vous verriez deux résultats différents.

Voici une implémentation avec int / long. C’est comme ça que Java tourne.

 int roundedNumber = (int)Math.Floor(d + 0.5); 

C’est probablement la méthode la plus efficace à laquelle vous pouvez penser.

Si vous voulez conserver un double et utiliser la précision décimale, alors il suffit d’utiliser des exposants de 10 basés sur le nombre de décimales.

 public double getRounding(double number, int decimalPoints) { double decimalPowerOfTen = Math.Pow(10, decimalPoints); return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen; } 

Vous pouvez saisir un nombre décimal négatif pour les points décimaux et son mot également.

 getRounding(239, -2) = 200 

Ce post a la réponse que vous recherchez:

http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx

Fondamentalement, c’est ce qu’il dit:

Valeur de retour

Le nombre le plus proche de la valeur avec une précision égale aux chiffres. Si value est à mi-chemin entre deux nombres, l’un étant pair et l’autre impair, le nombre pair est renvoyé. Si la précision de la valeur est inférieure aux chiffres, la valeur est renvoyée sans modification.

Le comportement de cette méthode est conforme à la norme IEEE 754, section 4. Ce type d’arrondi est parfois appelé arrondi au plus proche ou arrondi par le banquier. Si les chiffres sont zéro, ce type d’arrondi est parfois appelé arrondi vers zéro.

Silverlight ne prend pas en charge l’option MidpointRounding. Voici une méthode d’extension pour Silverlight qui ajoute l’énumération MidpointRounding:

 public enum MidpointRounding { ToEven, AwayFromZero } public static class DecimalExtensions { public static decimal Round(this decimal d, MidpointRounding mode) { return d.Round(0, mode); } ///  /// Rounds using arithmetic (5 rounds up) symmesortingcal (up is away from zero) rounding ///  /// A Decimal number to be rounded. /// The number of significant fractional digits (precision) in the return value. /// The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned. public static decimal Round(this decimal d, int decimals, MidpointRounding mode) { if ( mode == MidpointRounding.ToEven ) { return decimal.Round(d, decimals); } else { decimal factor = Convert.ToDecimal(Math.Pow(10, decimals)); int sign = Math.Sign(d); return Decimal.Truncate(d * factor + 0.5m * sign) / factor; } } } 

Source: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/

en utilisant un arrondi personnalisé

 public int Round(double value) { double decimalpoints = Math.Abs(value - Math.Floor(value)); if (decimalpoints > 0.5) return (int)Math.Round(value); else return (int)Math.Floor(value); } 

Manière simple est:

 Math.Ceiling(decimal.Parse(yourNumber + "")); 

Voici comment je devais le faire:

 Public Function Round(number As Double, dec As Integer) As Double Dim decimalPowerOfTen = Math.Pow(10, dec) If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then Return Math.Round(number, 2, MidpointRounding.AwayFromZero) Else Return CInt(number * decimalPowerOfTen + 0.5) / 100 End If End Function 

Essayer avec 1.905 avec 2 décimales donnera 1,91 comme prévu mais Math.Round(1.905,2,MidpointRounding.AwayFromZero) donne 1.90! La méthode Math.Round est absolument incohérente et inutilisable pour la plupart des problèmes de base rencontrés par les programmeurs. Je dois vérifier si (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2) car je ne veux pas arrondir ce qui devrait être arrondi.

C’est laid comme tout l’enfer, mais produit toujours un arrondi arithmétique correct.

 public double ArithRound(double number,int places){ ssortingng numberFormat = "###."; numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#'); return double.Parse(number.ToSsortingng(numberFormat)); }