Reputation: 7
I created a small piece of software in c# that calculates overtime in decimal format. I have designed it so that it always rounds down to the nearest tenth of an hour. The problem that I am having is that when I try to calculate a time that is exactly 2 hours 12 minutes or 3 hours 12 minutes I get an incorrect result. The relevant code follows:
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
and dtpTimeEnd
are all date time picker controls in a Win Forms project.
When I debug this code I find that the returned value of subtotalRounded
is either 2.1999999999999997 or 3.1999999999999997 instead of 2.2 and 3.2. This doesn't seem to happen with values of 4 hours 12 minutes or greater or 1 hour 12 minutes or less.
Anyways, I'm completely stumped why rounding error occurs with only these two values. Does anyone have any suggestions or comments? Am I using the method incorrectly or is there a better way to do this?
Upvotes: 0
Views: 768
Reputation: 7
You were all right about the floating point numbers. I converted everything to decimal and it works now. I'm sure there's a more elegant way to do this but here is my 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);
Using decimal makes the calculations come out correctly. I didn't know that using double would lead to floating point errors. Thank you to everyone who helped a new programmer out!
Upvotes: 0
Reputation: 26917
Certain numbers can't be represented exactly in a double
. You should either use decimal
or (preferably) work in minutes and then divide by 60 using decimal
on output.
Upvotes: 0
Reputation: 6795
You use a double
which is precise on its bit representation, not its decimal representation. Use the decimal
data type instead. Microsoft states this:
Avoid using float or real columns in WHERE clause search conditions, especially the = and <> operators. It is best to limit float and real columns to > or < comparisons.
Change these two code lines
double subtotalRounded = subtotal.TotalHours;
subtotalRounded = (Math.Floor(subtotalRounded * 10) / 10);
to this:
decimal subtotalRounded = subtotal.TotalHours; // See data type!
subtotalRounded = (Math.Floor(subtotalRounded * 10) / 10);
or even shorter this:
decimal subtotalRounded = = (Math.Floor(subtotal.TotalHours * 10) / 10);
You may need to do proper data type conversion. If so, you can use the helper methods from the Convert
class.
Upvotes: 2
Reputation: 15162
That is simply the way floating point values work, generally you deal with it when displaying rather than when calculating. That is when you do a string.Format("{0:f2}", value) it will then round to the requested number of digits.
Upvotes: 0