strategic.learner
strategic.learner

Reputation: 65

Why is subtracting int.MaxValue from uint different from subtracting an int typed variable from uint?

At least on the surface, this is not a question about overflowing int nor uint. I understand the values overlap by only ~half, and the ranges are equal. In fact I'm counting on that.

Background: Have a DotNet CRC hash algorithm that returns an uint, and I need to convert that for storage in sql (which lacks an uint). This conversion should always be possible as the total range of both are equal, even though the start and end points are different. There is no concern about reversing the conversion. I Debuggered this as DotNet 4.0 and 4.6.1 with same results.

I'm baffled here:

In the code examples below:
intForSqlStorageOK is successful.
But intForSqlStorageFail1 throws a runtime exception.
What's different about them?


{
    uint uIntToBeStoredInSql = 591071; //CRC32 hash result
    int intMaxValue = int.MaxValue;
    int intForSqlStorageOK = Convert.ToInt32(uIntToBeStoredInSql - intMaxValue - 1);

    //System.OverflowException: 'Value was either too large or too small for an Int32.'
    int intForSqlStorageFail1 = Convert.ToInt32(uIntToBeStoredInSql - int.MaxValue - 1);
    int intForSqlStorageFail2 = Convert.ToInt32(uIntToBeStoredInSql - Convert.ToUInt32(int.MaxValue) - 1);
    int intForSqlStorageFail3 = checked(Convert.ToInt32(uIntToBeStoredInSql - int.MaxValue - 1));
    int intForSqlStorageFail4 = unchecked(Convert.ToInt32(uIntToBeStoredInSql - int.MaxValue - 1));
    int intForSqlStorageFail5; //??? probably many others ???

    ///this SO led to this workaround, which is just clobbering the problem by adding memory. doesn't explain the difference above.
    ///https://stackoverflow.com/questions/26386201/subtracting-uint-and-int-and-constant-folding
    int intForSqlStorageOK2 = Convert.ToInt32(Convert.ToInt64(uIntToBeStoredInSql) - int.MaxValue - 1);

    ///Thanks ckuri in comments - this is way more terse and somehow still arrives at same result
    int intForSqlStorageOK3 = (int)uIntToBeStoredInSql - intMaxValue - 1;
    int intForSqlStorageOK4 = (int)uIntToBeStoredInSql - int.MaxValue - 1;

}

Thanks everyone for commenting! learned alot

Upvotes: 1

Views: 377

Answers (1)

user4003407
user4003407

Reputation: 22132

According to specification, int.MaxValue can be implicitly converted to uint type, while non-constant int expression can not, because at compile time compiler does not know if value of int expression within range of uint type or not.

If you apply rules for operator resolution, then you will see, that uIntToBeStoredInSql - intMaxValue - 1 is interpreted as (long)uIntToBeStoredInSql - (long)intMaxValue - 1L with overall value -2146892577L (which is in range of int type).

While uIntToBeStoredInSql - int.MaxValue - 1 is interpreted as uIntToBeStoredInSql - (uint)int.MaxValue - 1u with overall value 2148074719u (which is not in range of int type) in unchecked context and OverflowException exception in checked context.

Upvotes: 2

Related Questions