2017-12-26 17 views
0

Ich habe ein kleines Stück Software in C# erstellt, die Überstunden im Dezimalformat berechnet. Ich habe es so entworfen, dass es immer auf die nächste Zehntel Stunde abgerundet wird. Das Problem, das ich habe, ist, dass, wenn ich versuche, eine Zeit zu berechnen, die genau 2 Stunden 12 Minuten oder 3 Stunden 12 Minuten ist, bekomme ich ein falsches Ergebnis. Der entsprechende Code folgt:TimeSpan Berechnung Fehler

DateTime start = new DateTime(
         dtpDateStart.Value.Year, 
         dtpDateStart.Value.Month, 
         dtpDateStart.Value.Day, 
         dtpTimeStart.Value.Hour, 
         dtpTimeStart.Value.Minute, 
         0); 

     DateTime end = new DateTime(
      dtpDateEnd.Value.Year, 
      dtpDateEnd.Value.Month, 
      dtpDateEnd.Value.Day, 
      dtpTimeEnd.Value.Hour, 
      dtpTimeEnd.Value.Minute, 
      0); 

     TimeSpan subtotal = end - start; 

     double subtotalRounded = subtotal.TotalHours; 
     subtotalRounded = (Math.Floor(subtotalRounded * 10)/10); 

dtpDateStart, dtpTimeStart, dtpDateEnd und dtpTimeEnd sind alle Picker Kontrollen Datum Zeit in einem Projekt Win Forms.

Wenn ich diesen Code debuggen finde ich, dass der zurückgegebene Wert subtotalRounded entweder 2,1999999999999997 oder 3,1999999999999997 statt 2,2 und 3,2 ist. Dies scheint bei Werten von 4 Stunden 12 Minuten oder mehr oder 1 Stunde 12 Minuten oder weniger nicht zu passieren.

Wie auch immer, ich bin völlig ratlos warum Rundungsfehler nur mit diesen beiden Werten auftritt. Hat jemand irgendwelche Vorschläge oder Kommentare? Benutze ich die Methode falsch oder gibt es einen besseren Weg?

+3

Dies scheint genau wie ein Fließkomma "Fehler" (Zitate absichtlich).Wenn Sie keine Rundungsprobleme haben möchten (besonders im Dezimalbruch): Verwenden Sie double - so nicht: arbeiten Sie in Minuten (oder Sekunden, oder welche Genauigkeit Sie wollen) als Integer, oder wenn Sie wirklich einen verwenden möchten Dezimal so: Konvertieren von Minuten (Sekunden, was auch immer) in 'Dezimal' –

Antwort

0

Das ist einfach die Art und Weise, wie Gleitkommawerte funktionieren, normalerweise werden Sie beim Anzeigen und nicht beim Berechnen damit fertig. Das ist, wenn Sie eine Zeichenkette machen. Format ("{0: f2}", Wert) es wird dann auf die angeforderte Anzahl von Ziffern runden.

+3

Teilweise falsch. Das Format wird nicht gerundet. Es schneidet einfach die übrig gebliebenen Ziffern ab. Das Runden kann dazu führen, dass der Wert größer als der Cutoff ist, also ist es eine sehr wichtige Unterscheidung. – Christopher

2

Sie verwenden eine double, die präzise auf ihre Bit-Darstellung, nicht seine Dezimal-Darstellung ist. Verwenden Sie stattdessen den Datentyp decimal. diese Microsoft heißt es:

Vermeiden Sie float oder real Spalten in WHERE-Klausel Suchbedingungen, vor allem die = und <> Betreiber. Es ist am besten, float und real Spalten zu> oder < Vergleiche zu begrenzen.

ändern diese beiden Codezeilen

double subtotalRounded = subtotal.TotalHours; 
subtotalRounded = (Math.Floor(subtotalRounded * 10)/10); 

dazu:

decimal subtotalRounded = subtotal.TotalHours; // See data type! 
subtotalRounded = (Math.Floor(subtotalRounded * 10)/10); 

oder sogar kürzer diese:

decimal subtotalRounded = = (Math.Floor(subtotal.TotalHours * 10)/10); 

Unter Umständen müssen Sie die richtige Datentypumwandlung tun. Wenn dies der Fall ist, können Sie die Hilfsmethoden aus der Klasse Convert verwenden.

+1

Es klingt in der Tat wie die alten Floating Point Missconceptions. Dieses Video erklärt es gut: https://www.youtube.com/watch?v=PZRI1IfStY0 Im Grunde, wenn Sie Präzision benötigen, können Sie Schwimmer nie verwenden. – Christopher

0

Bestimmte Zahlen können nicht exakt in einer double dargestellt werden. Sie sollten entweder decimal oder (vorzugsweise) in Minuten arbeiten und dann durch 60 dividieren mit decimal am Ausgang.

0

Mit den Fließkommazahlen ging es Ihnen gut. Ich habe alles in Dezimal umgewandelt und es funktioniert jetzt. Ich bin sicher, dass es eine elegantere Möglichkeit, dies zu tun, aber hier ist mein Code:

 DateTime start = new DateTime(
         dtpDateStart.Value.Year, 
         dtpDateStart.Value.Month, 
         dtpDateStart.Value.Day, 
         dtpTimeStart.Value.Hour, 
         dtpTimeStart.Value.Minute, 
         0); 

     DateTime end = new DateTime(
      dtpDateEnd.Value.Year, 
      dtpDateEnd.Value.Month, 
      dtpDateEnd.Value.Day, 
      dtpTimeEnd.Value.Hour, 
      dtpTimeEnd.Value.Minute, 
      0); 

     TimeSpan subtotal = end - start; 

     decimal decSubtotalMinutes = Convert.ToDecimal(subtotal.TotalMinutes); 
     decSubtotalMinutes = decSubtotalMinutes/60; 
     decSubtotalMinutes = (Math.Floor(decSubtotalMinutes * 10)/10); 

dezimal Verwendung macht die Berechnungen korrekt herauskommen. Ich wusste nicht, dass die Verwendung von double zu Gleitkommafehlern führen würde. Danke an alle, die einem neuen Programmierer geholfen haben!