Determine if integer overflow is over or under bou

2019-08-07 05:46发布

问题:

Using C#, I have a few custom classes where I need to be able to detect integer overflows and return a default minimum or maximum value depending on if the overflow was due to the result being over the maximum value or under the minimum value. I can't seem to find a suggestion on how to detect the "type" of overflow that occurs anywhere.

The classes are divided between two general types: ones that use signed values, and ones that use unsigned values.

As an example, here is one of the classes that deals with Int32 values:

public class Stat32Tf : IStat32T<float>
{
    #region fields

    private int baseValue, baseAdjustment;
    private float baseMultiplier;

    #endregion

    #region ctors

    public Stat32Tf()
    {
        baseValue = 0;
        baseAdjustment = 0;
        baseMultiplier = 1f;
    }

    public Stat32Tf(int baseValue, int baseAdjustment = 0, float baseMultiplier = 1f)
    {
        this.baseValue = baseValue;
        this.baseAdjustment = baseAdjustment;
        this.baseMultiplier = baseMultiplier;
    }

    #endregion

    #region properties

    public int BaseValue
    {
        get 
        { 
            return baseValue; 
        }
        set 
        {
            baseValue = value; 
        }
    }

    public int BaseAdjustment
    {
        get 
        { 
            return baseAdjustment; 
        }
        set 
        { 
            baseAdjustment = value; 
        }
    }

    public float BaseMultiplier
    {
        get 
        { 
            return BaseMultiplier; 
        }
        set 
        { 
            baseMultiplier = value; 
        }
    }

    public int TruncValue
    {
        get 
        { 
            return (int)Value; 
        }
    }

    public float Value
    {
        get 
        { 
            return (baseValue + baseAdjustment) * baseMultiplier; 
        }
    }

    #endregion

}

As you can see, the idea of the class is to hold a base value, an adjustment value, and a multiplier value, and return the aggregate value in the Value property. (The TruncValue property just, as it suggests, returns the truncated whole value, dropping any fractional values).

The goal is to handle overflows in the "get" accessor of the Value property and, if the result is over the max int value, return int.MaxValue and if it is under the min value, return int.MinValue, all without throwing the actual overflow error. The part that's making it tricky for me is that the adjustment values and multipliers could be negative values as well (as per the design requirement).

What is a safe way to achieve this? I have not been able to find any resources that address this kind of situation. I'm guessing some sort of arithmetic algorithm will need to be used to determine of results will be over or under.

回答1:

Floats are pretty big. Are you expecting the get value to overflow or do you expect the cast to int to overflow? If it's just the cast something similar to the following code might work.

//This answer is wrong, see below.
public int TruncValue
{
    get
    {
        if (Value > (float)int.MaxValue)
        {
            return int.MaxValue
        }
        else if (Value < (float)int.MinValue)
        {
            return int.MinValue
        }
        else
        {
            return (int)Value;
        }
    }
}

Although you might need some additional handling for the edge cases.

Edit - I played around with this in some code and found some behavior that I didn't expect, but apparently it is in the specification.

For example,

var Value = int.MaxValue + int.MaxValue //Ends up returning -2 with no exception in debug mode.
var MaxCalculatedValue = (int.MaxValue + int.MaxValue) * float.MaxValue //Ends up returning something like -3.4... ^38.

You really might need to up cast everything into a double and then check to see if the result is greater than or less than an int.

So it might look something like this:

public float Value
{
    get
    {
        var result = ((double)baseValue + (double)baseAdjustment) * (double)baseMultiplier;
        if (result > (double)int.MaxValue)
        {
           return (float)int.MaxValue)
        }
        if (result < (double)int.MinValue)
        {
           return (float)int.MinValue)
        }
        return (float)result;
    }
}


回答2:

There are only a limited number of cases where it could underflow:

  • If baseValue and baseAdjustment are both negative -> If Int.MinValue - baseAdjustment > baseValue then you have an underflow.

  • If baseValue + baseAjustment is negative and baseMultiplier is positive -> If an overflow exception is raised, then it can only be an underflow.

  • If baseValue + baseAdjustment is positive but baseMultiplier is negative -> If an overflow exception is raised, then it can only be an underflow.

If you want to avoid raising/catching exception, then it might be a bit more complicated (you may want to cast the result as long and compare it against Int.MaxValue; that way it'll only raise an exception if the result goes over Long.MaxValue).