Warum gibt Math.Round (2.5) 2 statt 3 zurück?

415

In C # ist das Ergebnis von Math.Round(2.5)2.

Es soll 3 sein, nicht wahr? Warum ist es 2 stattdessen in C #?

Jeffu
quelle
5
Es ist eigentlich eine Funktion. Siehe <a href=" msdn.microsoft.com/en-us/library/… MSDN-Dokumentation</a> . Diese Art der Rundung wird als Banker-Rundung bezeichnet. Für eine Problemumgehung gibt es <a href = " msdn. microsoft.com/en-us/library/… Überladung </a>, mit der der Anrufer angeben kann, wie die Rundung durchgeführt werden soll.
Joe
1
Anscheinend gibt die Rundungsmethode, wenn sie aufgefordert wird, eine Zahl genau zwischen zwei Ganzzahlen zu runden, die gerade Ganzzahl zurück. Also, Math.Round (3.5) gibt 4 zurück. Siehe diesen Artikel
Matthew Jones
20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Robert Durgin
SQL Server rundet auf diese Weise ab. Interessante Testergebnisse bei einem C # -Einheitentest zur Validierung der in T-SQL durchgeführten Rundung.
Idstam
6
@amed das ist kein Bug. So funktionieren binäre Gleitkommazahlen. 1.005kann nicht genau doppelt dargestellt werden. Es ist wahrscheinlich 1.00499.... Wenn Sie Decimaldieses Problem verwenden, verschwindet es. Das Vorhandensein der Math.Round-Überladung, die eine Anzahl von Dezimalstellen im doppelten Bereich benötigt, ist eine zweifelhafte Entwurfsentscheidung, da sie selten auf sinnvolle Weise funktioniert.
CodesInChaos

Antworten:

560

Erstens wäre dies sowieso kein C # -Fehler - es wäre ein .NET-Fehler. C # ist die Sprache - sie entscheidet nicht darüber, wie sie Math.Roundimplementiert wird.

Und zweitens nein - wenn Sie die Dokumente lesen , werden Sie feststellen , dass die Standardrundung "Rund auf Gerade" ist (Bankrundung):

Rückgabewert
Typ: System.Double
Die Ganzzahl, die a am nächsten liegt. Wenn die Bruchkomponente von a auf halbem Weg zwischen zwei ganzen Zahlen liegt, von denen eine gerade und die andere ungerade ist, wird die gerade Zahl zurückgegeben. Beachten Sie, dass diese Methode einen Doubleanstelle eines ganzzahligen Typs zurückgibt .

Anmerkungen
Das Verhalten dieser Methode folgt dem IEEE-Standard 754, Abschnitt 4. Diese Art der Rundung wird manchmal als Rundung auf die nächste oder Bankrundung bezeichnet. Es minimiert Rundungsfehler, die sich aus der konsistenten Rundung eines Mittelpunkts in eine einzelne Richtung ergeben.

Sie können festlegen, wie Math.RoundMittelpunkte mit einer Überladung gerundet werden sollen, die einen MidpointRoundingWert annimmt. Es gibt eine Überladung mit einer MidpointRoundingentsprechenden Überladung, die keine hat:

Ob diese Standardeinstellung gut gewählt wurde oder nicht, ist eine andere Frage. ( MidpointRoundingwurde nur in .NET 2.0 eingeführt. Vorher war ich mir nicht sicher, ob es eine einfache Möglichkeit gab, das gewünschte Verhalten zu implementieren, ohne es selbst zu tun.) Insbesondere die Geschichte hat gezeigt, dass es nicht das erwartete Verhalten ist - und in den meisten Fällen ist dies der Fall eine Hauptsünde im API-Design. Ich kann sehen, warum Banker's Rounding nützlich ist ... aber für viele ist es immer noch eine Überraschung.

Vielleicht möchten Sie sich das nächste Java-Äquivalent enum ( RoundingMode) ansehen, das noch mehr Optionen bietet. (Es geht nicht nur um Mittelpunkte.)

Jon Skeet
quelle
4
Ich weiß nicht, ob dies ein Fehler ist, ich denke, es war beabsichtigt, da .5 so nahe an der nächstniedrigeren Ganzzahl liegt wie an der nächsthöheren Ganzzahl.
Stan R.
3
Ich erinnere mich an dieses Verhalten in VB, bevor .NET angewendet wurde.
John Fiala
7
In der Tat, IEEE Standard 754, Abschnitt 4, wie in der Dokumentation angegeben.
Jon Skeet
2
Ich habe mich vor einiger Zeit davon verbrannt und dachte, es sei auch Wahnsinn. Glücklicherweise haben sie eine Möglichkeit hinzugefügt, die Rundung festzulegen, die wir alle in der Grundschule gelernt haben. MidPointRounding.
Shea
26
+1 für "es ist nicht das erwartete Verhalten, [...] das eine
Hauptsünde
215

Dies wird als Rundung auf gerade (oder Bankrundung) bezeichnet. Dies ist eine gültige Rundungsstrategie zur Minimierung von aufgelaufenen Fehlern in Summen (MidpointRounding.ToEven). Die Theorie ist, dass, wenn Sie eine 0,5-Zahl immer in die gleiche Richtung runden, die Fehler schneller auftreten (Round-to-Even soll dies minimieren) (a) .

Folgen Sie diesen Links für die MSDN-Beschreibungen von:

  • Math.Floor, die in Richtung negative Unendlichkeit abrundet.
  • Math.Ceiling, was sich in Richtung positive Unendlichkeit aufrundet.
  • Math.Truncate, die gegen Null auf- oder abrundet.
  • Math.Round, die auf die nächste Ganzzahl oder die angegebene Anzahl von Dezimalstellen rundet. Sie können das Verhalten angeben, wenn es zwischen zwei Möglichkeiten genau gleich weit entfernt ist, z. B. gerundet, damit die letzte Ziffer gerade ist (" Round(2.5,MidpointRounding.ToEven)" 2 wird) oder weiter von Null entfernt ist (" Round(2.5,MidpointRounding.AwayFromZero)" 3 wird).

Das folgende Diagramm und die folgende Tabelle können hilfreich sein:

-3        -2        -1         0         1         2         3
 +--|------+---------+----|----+--|------+----|----+-------|-+
    a                     b       c           d            e

                       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

Beachten Sie, dass dies Roundviel leistungsfähiger ist, als es scheint, einfach weil es auf eine bestimmte Anzahl von Dezimalstellen gerundet werden kann. Alle anderen runden immer auf null Dezimalstellen. Zum Beispiel:

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

Bei den anderen Funktionen müssen Sie Multiplikations- / Divisions-Tricks verwenden, um den gleichen Effekt zu erzielen:

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

(ein) Diese Theorie hängt natürlich von der Tatsache ab, dass Ihre Daten eine ziemlich gleichmäßige Verteilung der Werte über die geraden Hälften (0,5, 2,5, 4,5, ...) und ungeraden Hälften (1,5, 3,5, ...) aufweisen.

Wenn alle "Halbwerte" (zum Beispiel) gleich sind, häufen sich die Fehler genauso schnell an, als ob Sie immer aufgerundet hätten.

paxdiablo
quelle
3
Auch bekannt als Banker's Rounding
Pondidum
Gute Erklärung! Ich wollte selbst sehen, wie sich der Fehler ansammelt, und habe ein Skript geschrieben, das zeigt, dass Werte, die mit Banker-Rundungen gerundet wurden, auf lange Sicht ihre Summen und Durchschnittswerte viel näher an diesen ursprünglichen Werten haben. github.com/AmadeusW/RoundingDemo (Bilder von Grundstücken verfügbar)
Amadeusz Wieczorek
Nur kurze Zeit später: Sollte das eHäkchen (= 2,8) nicht weiter rechts sein als das 2Häkchen?
Superjos
Eine einfache Art, sich zu erinnern, und unter der Annahme, dass der zehnte Platz 5 ist: - Ein Platz und der zehnte Platz sind alle ungerade = Aufrunden - Ein Platz und Zehnter Platz sind gemischt = Aufrunden * Null ist nicht ungerade * Umgekehrt für negative Zahlen
Arkham Angel
@ArkhamAngel, das scheint tatsächlich schwieriger zu merken, als nur "die letzte Ziffer gerade zu machen" :-)
paxdiablo
42

Von MSDN gibt Math.Round (double a) Folgendes zurück:

Die ganze Zahl am nächsten a. Wenn die Bruchkomponente von a auf halbem Weg zwischen zwei ganzen Zahlen liegt, von denen eine gerade und die andere ungerade ist, wird die gerade Zahl zurückgegeben.

... und so wird 2.5 auf halbem Weg zwischen 2 und 3 auf die gerade Zahl (2) abgerundet. Dies wird als Banker-Rundung bezeichnet (oder Round-to-Even) bezeichnet und ist ein häufig verwendeter Rundungsstandard.

Gleicher MSDN-Artikel:

Das Verhalten dieser Methode folgt dem IEEE-Standard 754, Abschnitt 4. Diese Art der Rundung wird manchmal als Rundung auf die nächste oder Bankrundung bezeichnet. Es minimiert Rundungsfehler, die sich aus der konsistenten Rundung eines Mittelpunkts in eine einzelne Richtung ergeben.

Sie können ein anderes Rundungsverhalten angeben, indem Sie die Überladungen von Math.Round aufrufen, die einen MidpointRoundingModus annehmen .

Michael Petrotta
quelle
37

Sie sollten MSDN auf Folgendes überprüfen Math.Round:

Das Verhalten dieser Methode folgt dem IEEE-Standard 754, Abschnitt 4. Diese Art der Rundung wird manchmal als Rundung auf die nächste oder Bankrundung bezeichnet.

Sie können das Verhalten bei Math.RoundVerwendung einer Überladung angeben :

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

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2
Dirk Vollmar
quelle
31

Die Art der Rundung

Betrachten Sie die Aufgabe, eine Zahl, die einen Bruch enthält, auf beispielsweise eine ganze Zahl zu runden. Unter diesen Umständen wird gerundet, um festzustellen, welche ganze Zahl die von Ihnen gerundete Zahl am besten darstellt.

In der allgemeinen oder "arithmetischen" Rundung ist klar, dass 2.1, 2.2, 2.3 und 2.4 auf 2.0 runden; und 2,6, 2,7, 2,8 und 2,9 bis 3,0.

Damit bleibt 2,5 übrig, was nicht näher an 2,0 liegt als an 3,0. Es liegt an Ihnen, zwischen 2.0 und 3.0 zu wählen, beides wäre gleichermaßen gültig.

Für Minuszahlen würden -2,1, -2,2, -2,3 und -2,4 -2,0 werden; und -2,6, 2,7, 2,8 und 2,9 würden unter arithmetischer Rundung zu -3,0 werden.

Für -2,5 wird eine Auswahl zwischen -2,0 und -3,0 benötigt.

Andere Formen der Rundung

'Aufrunden' nimmt eine beliebige Zahl mit Dezimalstellen und macht sie zur nächsten 'ganzen' Zahl. Machen Sie also nicht nur 2.5 und 2.6 auf 3.0, sondern auch 2.1 und 2.2.

Durch Aufrunden werden sowohl positive als auch negative Zahlen von Null wegbewegt. Z.B. 2,5 bis 3,0 und -2,5 bis -3,0.

Durch Abrunden werden Zahlen abgeschnitten, indem unerwünschte Ziffern abgeschnitten werden. Dies hat den Effekt, dass Zahlen gegen Null verschoben werden. Z.B. 2,5 bis 2,0 und -2,5 bis -2,0

Bei der "Bankrundung" - in der gebräuchlichsten Form - wird die zu rundende .5 entweder nach oben oder nach unten gerundet, so dass das Ergebnis der Rundung immer eine gerade Zahl ist. Also 2,5 Runden auf 2,0, 3,5 bis 4,0, 4,5 bis 4,0, 5,5 bis 6,0 und so weiter.

'Alternatives Runden' wechselt den Vorgang für jede .5 zwischen Abrunden und Aufrunden.

'Zufällige Rundung' rundet eine .5 ganz zufällig nach oben oder unten.

Symmetrie und Asymmetrie

Eine Rundungsfunktion wird als "symmetrisch" bezeichnet, wenn sie entweder alle Zahlen von Null weg oder alle Zahlen gegen Null rundet.

Eine Funktion ist 'asymmetrisch', wenn positive Zahlen gegen Null und negative Zahlen von Null weg gerundet werden. 2,5 bis 2,0; und -2,5 bis -3,0.

Ebenfalls asymmetrisch ist eine Funktion, die positive Zahlen von Null und negative Zahlen auf Null rundet. Z.B. 2,5 bis 3,0; und -2,5 bis -2,0.

Die meisten Menschen denken an symmetrische Rundungen, bei denen -2,5 auf -3,0 und 3,5 auf 4,0 gerundet werden. (in C #Round(AwayFromZero))

Patrick Peters
quelle
28

Die Standardeinstellung MidpointRounding.ToEvenoder Bankerrundung ( 2,5 wird zu 2, 4,5 wird zu 4 usw.) ) hat mich schon früher beim Schreiben von Berichten für die Buchhaltung gestochen, daher schreibe ich ein paar Worte über das, was ich zuvor herausgefunden habe und worauf ich mich eingelassen habe dieser Beitrag.

Wer sind diese Banker, die gerade Zahlen abrunden (britische Banker vielleicht!)?

Aus Wikipedia

Der Ursprung des Begriffs Bankrundung bleibt unklarer. Wenn diese Rundungsmethode jemals ein Standard im Bankwesen war, haben sich die Beweise als äußerst schwer zu finden erwiesen. Im Gegenteil, Abschnitt 2 des Berichts der Europäischen Kommission Die Einführung des Euro und die Rundung der Währungsbeträge legen nahe, dass es bisher keinen Standardansatz für die Rundung im Bankwesen gegeben hat. und es gibt an, dass "halbe" Beträge aufgerundet werden sollen.

Es scheint eine sehr seltsame Art der Rundung zu sein, insbesondere für das Bankwesen, es sei denn, die Banken erhalten natürlich viele Einlagen mit gleichmäßigen Beträgen. Zahlen Sie 2,4 Mio. £ ein, aber wir nennen es 2 Mio. £, Sir.

Der IEEE-Standard 754 stammt aus dem Jahr 1985 und bietet beide Rundungsmöglichkeiten, jedoch mit Banker als vom Standard empfohlen. Dieser Wikipedia-Artikel enthält eine lange Liste, wie Sprachen Rundungen implementieren (korrigieren Sie mich, wenn eine der folgenden Angaben falsch ist). Die meisten verwenden keine Banker, sondern die Rundungen, die Sie in der Schule lernen:

  • C / C ++ round () von math.h rundet von Null weg (keine Bankerrundung)
  • Java Math.Round rundet von Null ab (es gibt das Ergebnis an, addiert 0,5 und wird in eine Ganzzahl umgewandelt). In BigDecimal gibt es eine Alternative
  • Perl verwendet einen ähnlichen Weg wie C.
  • Javascript ist dasselbe wie Javas Math.Round.
Chris S.
quelle
Danke für die Auskunft. Ich habe das nie bemerkt. Ihr Beispiel über die Millionen macht es ein bisschen lächerlich, aber selbst wenn Sie auf Cent runden, kostet die Zahlung von Zinsen auf 10 Millionen Bankkonten die Bank viel, wenn alle halben Cent aufgerundet werden, oder kostet die Kunden viel, wenn alle halbe Cent werden abgerundet. Ich kann mir also vorstellen, dass dies der vereinbarte Standard ist. Ich bin mir nicht sicher, ob dies wirklich von Bankern genutzt wird. Die meisten Kunden werden keine Abrundung bemerken, während sie viel Geld
einbringen
15

Von MSDN:

Standardmäßig verwendet Math.Round MidpointRounding.ToEven. Die meisten Menschen sind nicht mit "Rundung auf Gerade" als Alternative vertraut. "Abrunden von Null" wird in der Schule häufiger unterrichtet. .NET verwendet standardmäßig "Auf gerade runden", da es statistisch überlegen ist, da es nicht die Tendenz teilt, "von Null wegzurunden", etwas häufiger aufzurunden als abzurunden (vorausgesetzt, die gerundeten Zahlen sind tendenziell positiv. )

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

Cristian Donoso
quelle
3

Da Silverlight die MidpointRounding-Option nicht unterstützt, müssen Sie Ihre eigene schreiben. Etwas wie:

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;

}

Beispiele, einschließlich der Verwendung als Erweiterung, finden Sie im Beitrag: .NET und Silverlight Rounding

JBrooks
quelle
3

Ich hatte dieses Problem, bei dem mein SQL Server 0,5 zu 1 aufrundet, während meine C # -Anwendung dies nicht tat. Sie würden also zwei unterschiedliche Ergebnisse sehen.

Hier ist eine Implementierung mit int / long. So rundet Java.

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

Es ist wahrscheinlich die effizienteste Methode, die Sie sich vorstellen können.

Wenn Sie das Doppelte beibehalten und die Dezimalgenauigkeit verwenden möchten, müssen Sie nur Exponenten von 10 verwenden, basierend auf der Anzahl der Dezimalstellen.

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

Sie können eine negative Dezimalstelle für Dezimalstellen eingeben, und das Wort ist ebenfalls in Ordnung.

getRounding(239, -2) = 200
ShortFuse
quelle
1

Einfacher Weg ist:

Math.Ceiling(decimal.Parse(yourNumber + ""));
Nalan Madheswaran
quelle
0

Dieser Beitrag hat die Antwort, die Sie suchen:

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

Im Grunde ist es das, was es sagt:

Rückgabewert

Der nächstgelegene Wert mit einer Genauigkeit von Ziffern. Wenn der Wert auf halbem Weg zwischen zwei Zahlen liegt, von denen eine gerade und die andere ungerade ist, wird die gerade Zahl zurückgegeben. Wenn die Genauigkeit des Werts kleiner als die Ziffern ist, wird der Wert unverändert zurückgegeben.

Das Verhalten dieser Methode folgt dem IEEE-Standard 754, Abschnitt 4. Diese Art der Rundung wird manchmal als Rundung auf die nächste oder Bankrundung bezeichnet. Wenn die Ziffern Null sind, wird diese Art der Rundung manchmal als Rundung gegen Null bezeichnet.

Vaccano
quelle
0

Silverlight unterstützt die Option MidpointRounding nicht. Hier ist eine Erweiterungsmethode für Silverlight, mit der die MidpointRounding-Enumeration hinzugefügt wird:

public enum MidpointRounding
{
    ToEven,
    AwayFromZero
}

public static class DecimalExtensions
{
    public static decimal Round(this decimal d, MidpointRounding mode)
    {
        return d.Round(0, mode);
    }

    /// <summary>
    /// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
    /// </summary>
    /// <param name="d">A Decimal number to be rounded.</param>
    /// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
    /// <returns>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.</returns>
    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;
        }
    }
}

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

jascur2
quelle
-1

mit einer benutzerdefinierten Rundung

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);
}
anand360
quelle
>.5erzeugt das gleiche Verhalten wie Math.Round. Die Frage ist, was passiert, wenn der Dezimalteil genau ist 0.5. Mit Math.Round können Sie die Art des gewünschten Rundungsalgorithmus angeben
Panagiotis Kanavos
-2

Das ist höllisch hässlich, führt aber immer zu einer korrekten arithmetischen Rundung.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

  numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');

  return double.Parse(number.ToString(numberFormat));

}
James Montagne
quelle
5
Dies gilt auch für das Aufrufen Math.Roundund Festlegen, wie es gerundet werden soll.
Konfigurator
-2

Hier ist die Art und Weise, wie ich es umgehen musste:

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

Wenn Sie mit 1.905 mit 2 Dezimalstellen versuchen, erhalten Sie wie erwartet 1,91, aber Math.Round(1.905,2,MidpointRounding.AwayFromZero)1,90! Die Math.Round-Methode ist absolut inkonsistent und für die meisten grundlegenden Probleme, auf die Programmierer stoßen können, unbrauchbar. Ich muss prüfen, ob (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)ich nicht aufrunden möchte, was abgerundet werden soll.

MikeMuffinMan
quelle
Math.Round(1.905,2,MidpointRounding.AwayFromZero)Rückkehr1.91
Panagiotis Kanavos