2010-01-25 4 views
31

Ich möchte eine Rundungsmethode für doppelte Werte in C#. Es muss in der Lage sein, einen doppelten Wert auf einen Rundungsgenauigkeitswert zu runden. Mein Code auf der Hand wie folgt aussieht:Runden von doppelten Werten in 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); 
} 

So RoundI (100, 3) 99 geben soll und RoundI (1,2345, 0,001) sollte 1,235 geben.

Das Problem ist, RoundI (1,275, 0,01) gibt 1,27 statt 1,28 zurück. Dies liegt daran, dass bei der Ausführung von double valIntvRatio = val/intv, also double valIntvRatio = 1.275/0.01, 0,12749999999999 angegeben wird. Ich weiß, das ist ein Problem mit doppelter Repräsentation in irgendeiner Programmiersprache. Meine Frage ist, gibt es einen Standard-Code, um Dinge wie diese zu tun, ohne sich Gedanken über die Genauigkeit im Doppel zu machen? Hier setze ich die Toleranz auf 1e-14, aber das ist zu restriktiv für dieses Problem und ich weiß nicht, was die richtige Toleranz sein soll. Danke für jede Hilfe.

+5

Vielleicht sollten Sie den Dezimal-Datentyp verwenden. – Kibbee

+0

Warum würde Runde (100,3) 99 geben? Wenn Sie auf die gleiche Bruchposition wie die 3 (0 Stellen) runden, erhalten Sie 100, nicht 99. – paxdiablo

+0

paxdiablo: Entschuldigung, der Zweck von RoundI ist nicht zu runden, um den ersten Parameter auf dieselbe Bruchposition zu runden als zweiter Parameter. Der zweite Parameter ist das runde Intervall und die Rundung rundet den ersten Parameter auf den Schrankwert, der den Modus 0 hat, auf den zweiten Parameter. – Steve

Antwort

41

Beispiel decimal der Verwendung als Kibbee wies darauf hin,

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

Während diese großartige Lösung im Allgemeinen funktioniert, wenn die signifikanten Ziffern von "d" in der Nähe der Begrenzung der Genauigkeit eines "double" sind, entfernt die Umwandlung nach "dezimal" tatsächlich zu viel Präzision. Als Beispiel nimm 'd = 123456789.256;' (Du musst dies mit 'd.ToString (" R ")' ausgeben, um die "versteckte" Genauigkeit anzuzeigen). Wenn Sie in diesem Beispiel einfach 'Math.Round (d, 2)' verwenden (denken Sie daran, das Ergebnis mit '" R "' zu schreiben), erhalten Sie ein besseres Ergebnis als wenn Sie 'Math.Round ((dezimal) d, 2) '.Diese Umwandlung in "Dezimal" entfernt hier zu viel Präzision. In solchen Fällen benutze 'decimal' vom Anfang an, keine Umwandlungen. –

+0

+1 sehr schöne Lösung, aber es rundet leider nicht ** 0,005 ** bis ** 0,01 **. Das Ergebnis ist ** 0 ** –

+8

@Zefnus, weil es die Rundung des Bankers verwendet. Wenn Sie möchten, dass es auf 0,01 aufrundet, verwenden Sie 'Math.Round ((Dezimal) d, 2, MidpointRounding.AwayFromZero)' – Jimmy

5
double d = 1.2345; 

Math.Round(d, 2); 

der obige Code sollte es tun.

2

Wenn Sie tatsächlich double verwenden müssen, ersetzen Sie es unten und es wird funktionieren, aber mit den üblichen Genauigkeitsproblemen der binären Fließkomma-Arithmetik.

Es gibt sicherlich eine bessere Möglichkeit, die "Rundung" (fast eine Art Banker-Rundung) zu implementieren, als meine String-Jonglierung darunter.

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

genial ... fanks ... ersparte mir etwas Zeit, das eine zu arbeiten. – Jon

+0

möglicherweise einen Fehler gefunden. Wenn Sie übergeben: Nummer = 0,5 Rundungsintervall 1.0 Dinge gehen ein bisschen falsch mit der var Temp Array-Bit. – Jon

+0

Das stimmt, aber ein größerer Abstand als die Zahl macht keinen Sinn, oder? Ich habe einen zusätzlichen Check in der Methode hinzugefügt. –

2

Die Beispiele Dezimal-Casting in Jimmy ‚s Antwort zur Verfügung gestellt unter Verwendung nicht die Frage beantworten, da sie nicht zeigen, wie man einen doppelten Wert auf jede Rundung Genauigkeitswert abzurunden wie gewünscht. Ich glaube, die richtige Antwort dezimal Gießen ist die folgende:

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

Weil es dezimal Casting verwendet, ist diese Lösung unter den Gussfehlern erwähnt durch Jeppe Stig Nielsen in seinem Kommentar zu Jimmy ‚s Antwort .

Beachten Sie auch, dass ich MidpointRounding.AwayFromZero angegeben habe, da dies mit der Spezifikation des Anforderers übereinstimmt, dass RoundI (1,2345, 0,001) 1,235 ergeben sollte.