PaulD2999
PaulD2999

Reputation: 7

TimeSpan Calculation Error

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

Answers (4)

PaulD2999
PaulD2999

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

NetMage
NetMage

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

Quality Catalyst
Quality Catalyst

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

SoronelHaetir
SoronelHaetir

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

Related Questions