views:

133

answers:

2

I want to check whether a simple mathematical expression would overflow (using checked and catch(OverflowException)), but without the need to use a try-catch block every time. So the expression (not the result!) should be passed to a function checkOverflow, which then acts accordingly in case of an overflow.

This is what I attempted, but does not work as there doesn't seem to be lexical scoping for lambda expressions.

static void Main(string[] args)
{
    double g, h;

    // Check if the expression g+h would overflow, *without* the need to use
    // try/catch around the expression

    // Both of the following attempts fail because there's no lexical scoping
    checkOverflow((FloatReturningExpression) delegate() {return g+h;});
    checkOverflow(() => g+h);

    Console.ReadKey();
}

private static void checkOverflow(FloatReturningExpression exp)
{
    try
    {
        checked { double f = exp(); }
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

private delegate double FloatReturningExpression();

Is there any solution for that? (Working with .NET 2, but not necessarily.)

+4  A: 

Well floating point numbers in .Net don't overflow in the way integer arithmetic can.

They helpfully go to Double.PositiveIfinity, Double.NegativeIfinity or (specific to in cases where the mathematical operation becomes invalid Double.Nan)

note that this is also somehwat more complex because of the floating point behaviour when faced with two numbers with very different precision.

Console.WriteLine(double.MaxValue);
Console.WriteLine(double.MaxValue * 2);
Console.WriteLine(double.MaxValue + 1);
Console.WriteLine(double.MaxValue + double.MaxValue);

gives:

1.79769313486232E+308
Infinity
1.79769313486232E+308
Infinity

Also it is not clear what you want your checkOverflow funtion to do, just write that it happened?

If that's all this approach will work (I converted to int for you)

void Main()
{
    int a, b;
    a = int.MaxValue;
    b = 1;

    // Check if the expression a+b would overflow, *without* the need to use
    // try/catch around the expression
    checkOverflow(() => {checked { return a+b; }});    
}       

private static void checkOverflow(Func<int> exp)
{
    try
    {
        exp();
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

I should add the reason why this works:
checked is not a lexical scope in the sense of affecting variables. It is a region interpreted by the compiler as saying, all code inside here which does integer arithmetic should generate the overflow trapping instructions. it doesn't matter where the variables come from, only what code is defined where.

I believe your mental model is something like:

checked  // enter a 'checked' state where all operations 
{        // (say on the current thread) are checked

code, function calls, etc. etc

}  // leave the checked mode, all operations are now unchecked

This is not how checked works, checked defines what instructions are emitted at compile time (some instructions trap overflow, some do not)

The checked block does NOT affect code defined outside of it. For example just using functions:

int Times2(int a)
{
    return a * 2;
}

void TheresNoDifferenceHere()
{
    checked { Times2(int.MaxValue); }
    Times2(int.MaxValue);
}

The Times2 function call resolves down to something like

IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  ldc.i4.2    
IL_0003:  mul         
IL_0004:  stloc.0     
IL_0005:  br.s        IL_0007
IL_0007:  ldloc.0     
IL_0008:  ret   

If you had used

int Times2(int a)
{
    checked { return a * 2; }
}

IL_0000:  nop         
IL_0001:  nop         
IL_0002:  ldarg.1     
IL_0003:  ldc.i4.2    
IL_0004:  mul.ovf     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

note the difference in using mul and mul.ovf. Thus the two calls to it cannot change it to be checked or not after the fact. The checked block around the first call in the example above actually has no effect on the resulting IL. There are no operations defined inside it that matter to it.

Thus your original idea was defining the arithmetic operation in one place (without checking) then running it at another point (just like a function call) but the 'checked' instruction cannot affect code that it did not surround at compile time.

lambdas resolve down to either expression trees or an anonymous delegate (perhaps backed by the required synthetic classes to hold and maintain any closure related variables). In both cases the checked aspect of any part of them is entirely defined where they are defined, not where they are called.

ShuggyCoUk
Can you explain why the overflow is not caught when doing `checked { exp(); }` in the try block *instead* of in the lambda? When is `a+b` evaluated?
AndiDog
@AndiDog, does the bit in my edit not explain enough?I'll try to add to it.
ShuggyCoUk
Okay, makes sense now. Thanks
AndiDog
A: 

The problem here is that your checking for something that will never happen. An overflow doesn't occur in double (or float for that matter). Using your code if I set g and h to double.MaxValue then f becomes the expected result of (positive) infinity. You wont get an overflow on a double.

Leom Burke
this is true, but is not the problem with the concept. changing all doubles to ints would not make this work
ShuggyCoUk
yep, fair cop - +1 to your answer for being much more detailed and correct about the concept :)
Leom Burke