views:

182

answers:

4

I'm having an issue trying to divide two decimals and then display the result. Annoyingly this is only happening on our server, and it appears to work perfectly fine if I run the code locally. This is the code that I am trying to run


decimal dOne = -966.96M;
decimal dTwo = 2300M;

decimal dResult = Decimal.Round((dOne / dTwo), 28, 
                               MidpointRounding.AwayFromZero);

The resulting number (as generated from windows calculator) is

-0.43346086956521739130434782608696

This always results in an overflow exception:

System.OverflowException: Value was either too large or too small for a Decimal.
   at System.Decimal.FCallDivide(Decimal& result, Decimal d1, Decimal d2)
   at System.Decimal.op_Division(Decimal d1, Decimal d2)

This does kind of make sense as the resulting number is over 32 decimal places long, and a decimal can only hold up to 28 places..but I am not sure how to perform this division as it appears to be storing the result in the decimal type in memory, before rounding it off and storing it. I've also tried converting it directly to a string rather than storing it in a decimal, but that has the same problem.

Any ideas? Have I done something obviously silly (most probably) and is there a better way to perform this calculation?

+2  A: 

That should run fine. The division isn't guaranteed to return the exactly accurate version - for example, 1 / 3m works fine.

The result clearly isn't outside the range of decimal, so it looks to me like something weird is going on on your server.

One thing to check: is it Decimal.Round that's throwing the exception, or the division itself? Put them into separate statements to find out.

Jon Skeet
Thanks for the comment jon - I originally had them on separate lines and have tried it with and without the Decimal.Round, so I am pretty sure that the rounding is not the issue. It's definitely odd though as the exact same code works in sandbox and on my desktop..frustrating issue ;)
Spud1
+2  A: 

Try converting to double before calculating, and back to decimal afterwards if you need:

decimal dOne = -966.96M;
decimal dTwo = 2300M;

double one = (double)dOne;
double two = (double)dTwo;

double result = one / two;

decimal dResult = (decimal)result; // Additional rounding may be necessary
Tomas Lycken
I'm marking this as my answer, as after talking with my colleagues this is only happening in this specific case, and we can cope with the loss of precision. Have elected to put in a try/catch for the overflow and convert to a double if it happens for now..although if there is a better answer i'd rather put that in in the future :)
Spud1
+1  A: 

I looked at Decimal.Round via Reflector and from what I see it does not ever throw OverflowException so I am betting the exception is coming from the division. Can you edit your answer to include the stack trace?

Also, are you absolutely certain that the numerator and denominator are exactly as you have written? Try tracing the them to the console or a log file when the exception occurs.

You could do something like this:

decimal dOne = -966.96M; 
decimal dTwo = 2300M;  
try
{
  decimal dResult = Decimal.Round((dOne / dTwo), 28, MidpointRounding.AwayFromZero); 
}
catch (OverflowException)
{
  Console.WriteLine(dOne);
  Console.WriteLine(dTwo);
}

Edit: I think I found the code to FCallDivide in the SSCLI. However, it is likely to be different in the release version of the .NET Framework, but I can see from the way it was done in the SSCLI anyway that an overflow exception will be generated in a lot of different ways. The code is quite complicated. If you can construct a short but complete program demonstrating the problem I would submit it as a bug to Microsoft. It is possible that there is a certain bit pattern in those inputs that is confusing the algorithm.

Brian Gideon
I've added a bit more info - after that it goes back to other code which is unrelated. I added some logging to pull out the numbers before the divide and those are the ones it is using.At the moment I am looking at the double conversion solution posted by Tomas, as I don't really need more than around 10/12 DP as a precision here, and that looks like it will work..but need to test it more.
Spud1
Okay, it is clearly happening inside the division. This is quite odd. What version of the framework are you using locally and on the server? I will try to think of other ways you can troubleshoot this, but it is going to be difficult. I'll see if I can't find the code for the `FCallDivide` method.
Brian Gideon
+1  A: 

If it happens on your sever (where you can't debug). Are you really sure that the problem is within these lines?

Maybe you can put a try-catch statement around only the single Decimal.Round statement and giving back some weird value instead. This code could you run on your server again to see if this catch statement really gets called or if the Exception maybe happens somewhere else.

Oliver