6

Is it safe to cast from double to decimal in the following manner in C#:

int downtimeMinutes = 90;
TimeSpan duration = TimeSpan.FromHours(2d);
decimal calculatedDowntimePercent = duration.TotalMinutes > 0?
    (downtimeMinutes / (decimal)duration.TotalMinutes) * 100.0m : 0.0m;

If the answer is yes, then no fuss, I'll just mark as accepted.

Lisa
  • 4,333
  • 2
  • 27
  • 34
  • "Safe" in what sense - what risks are you worried about? Numerical imprecision? Runtime exceptions? Something else? – Karl Knechtel Jul 27 '11 at 04:20
  • Runtime exceptions were my only concern. I wondered if double always successfully casts to decimal. – Lisa Jul 27 '11 at 05:02

3 Answers3

4

No, in general, casting from double to decimal is not always safe:

[TestCase(double.MinValue)]
[TestCase(double.MaxValue)]
[TestCase(double.NaN)]
[TestCase(double.NegativeInfinity)]
[TestCase(double.PositiveInfinity)]
public void WillFail(double input)
{
    decimal result = (decimal)input; // Throws OverflowException!
}

As OP clarified in a comment to the question, "safe" being "doesn't cause run time exceptions", the above shows that exceptions can occur when casting a double to a decimal.


The above is the generic answer many Googlers might've come here for. However, to also answer the specific question by OP, here's a strong indication that the code will not throw exceptions, even on edge cases:

[Test]
public void SpecificCodeFromOP_WillNotFail_NotEvenOnEdgeCases()
{
    int downtimeMinutes = 90;
    foreach (TimeSpan duration in new[] {
        TimeSpan.FromHours(2d), // From OP
        TimeSpan.MinValue,
        TimeSpan.Zero,
        TimeSpan.MaxValue })
    {
        decimal calculatedDowntimePercent = duration.TotalMinutes > 0 ?
            (downtimeMinutes / (decimal)duration.TotalMinutes) * 100.0m : 0.0m;
    }
}
Jeroen
  • 60,696
  • 40
  • 206
  • 339
4

In general, double -> decimal conversions aren't safe, because decimal has a smaller range.

However, as long as TotalMinutes is less than the maximum decimal value* it will be fine. This is true, because TimeSpan.MaxValue.TotalMinutes < (double)decimal.MaxValue (I believe TimeSpan uses a long internally.)

So: yes.

*: (79,228,162,514,264,337,593,543,950,335 minutes is 1.1×10^13 times the age of the universe)

porges
  • 30,133
  • 4
  • 83
  • 114
  • In my domain the realistic maximum for the TotalMinutes is something like 4,000,000 but with hard absolute upper limit of around 1,000,000,000. So yes, it will be safe in my scenario. – Lisa Jul 27 '11 at 05:06
3

Yes it is safe, because decimal has greater precision

http://msdn.microsoft.com/en-us/library/364x0z75(VS.80).aspx

The compiler will put in casts around the other non decimal numbers, but they'll all fit into decimal * (see caveat).

-- Caveat

  • Decimal is not a floating point type. Its mandate is to always uphold precision. Whereas a floating point number such as double (which I mostly use) makes a tradeoff on precision to accommodate very large numbers). Very large or very small numbers will not fit into decimal. So Lisa needs to ask herself if the magnitude of the operation is likely to be less than 28 significant digital digits. 28 significant digits are adequate for most scenarios.

  • Floating point is good for astronomically large or infintessimally small numbers... or operations inbetween that yield enough accuracy. I should look this up, but double is okay for plus or minus a few billion with accuracy of up to several decimal points (up to 7 or 8?).

  • in the sciences there's no point measuring beyond the accuracy of your equipment. In finance, often the logical choice is double because a double is computationally more efficient for most situations (sometimes they want a bit more accuracy, but the efficiency is not worth throwing away for something like decimal). In the end we all have to get pragmatic and map business needs to a digital domain. There are tools out there that have a dynamic number representation. Probably there are libraries in .net for the same. However, is it worth it? Sometimes it is. Often it's overkill.

sgtz
  • 8,849
  • 9
  • 51
  • 91
  • 1
    Greater precision, but double has a much higher range. – Yuriy Faktorovich Jul 27 '11 at 04:23
  • 4
    "they'll all fit into decimal" isn't true: `Convert.ToDecimal(double.MaxValue)` will throw `OverflowException` – porges Jul 27 '11 at 04:30
  • see edit. For most scenarios. I'm being pragmatic, not a literalist here + you'll see that I give the extra info in the post. You need to match the business need to the problem domain, and make the appropriate tradeoffs. – sgtz Jul 27 '11 at 04:39
  • In this case the double does not need to be significantly precise. I'm forced to use it by time Span.TotalMinutes in my real life code. This will never be outside the range of 28 digits (because we do not consider timespans in the business domain to be of greater precision than 1 minute). But worth noting these caveats for other cases. – Lisa Jul 27 '11 at 05:00
  • when I saw "TotalMinutes", I though that *might* be the case ;-) – sgtz Jul 27 '11 at 05:09