1

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

  • You don't need to manually convert the input ranges, just do `(int)yourUint`. If the value is larger than int.MaxValue it will automatically become a negative int value, e.g. uint.MaxValue will become int -1. – ckuri Jan 13 '19 at 04:25
  • @ckuri - still counter intuitive in my head - but it works! thanks! – strategic.learner Jan 13 '19 at 04:45
  • @ckuri: Unless you need to do comparisons in SQL, in which case you need to also XOR the MSB/sign bit to preserve the ordering. – Ben Voigt Jan 13 '19 at 04:46
  • I'll give you something to ponder to see whether it gets you closer to an understanding. What happens to your code execution if you put `const` before `int intMaxValue = int.MaxValue;` **Why** do you think it does that? – mjwills Jan 13 '19 at 04:47
  • @mjwills - **What did the code do?** **What did you expect it to do?** **Why did you expect it to do that?** – strategic.learner Jan 13 '19 at 04:49
  • @mjwills - I think that puts me on the path to finding the answer now - didn't think of int.MaxValue as inherently immutable, but yeah duh. And I did see const mentioned on other threads - thanks! – strategic.learner Jan 13 '19 at 04:55
  • @Ben Voigt - ckuri's suggestion seems to work fine, at least in the overall function I have above. Guess it's because I'm converting to an int first in C#, then writing to Sql. – strategic.learner Jan 13 '19 at 05:11
  • It does work fine, if you don't use the converted values for comparisons in SQL queries, only for conversion back to `uint`. If you did comparisons in SQL, you would find that `(int)1 > (int)uint.MaxValue` (wrong answer). – Ben Voigt Jan 13 '19 at 05:14
  • @Ben Voight - as long as every value going to sql receives the identical conversion factor; they are very much comparable within sql. OTOH if I used an UNconverted uint in C# to directly compare with a converted Sql int - then yes the comparison would be invalid. – strategic.learner Jan 13 '19 at 18:03
  • @strategic.learner: Only for equality comparison. The ordering will not be preserved by a simple cast to int. See the example in my comment. – Ben Voigt Jan 13 '19 at 18:04
  • Oh BTW you can now add `int.MinValue` instead of subtracting `int.MaxValue` and another 1. – Ben Voigt Jan 13 '19 at 20:09
  • @Ben Voight - lol, finally understand what your driving at. 1) My Use-case is a hash value, so ordering is a non-concern, only equality. 2) Ordering IS preserved by subtracting int.MaxValue - (int)1. The range is always being shifted by exactly half. ((uint)0 => int.minvalue) (uint.maxvalue/2 => (int)0) (uint.MaxValue => int.MaxValue) – strategic.learner Jan 13 '19 at 20:17
  • bool min = int.MinValue.Equals(unchecked((int)uint.MinValue - int.MaxValue - (int)1)); bool mid = (-1).Equals(unchecked((int)(uint.MaxValue / 2) - int.MaxValue - (int)1)); bool max = int.MaxValue.Equals(unchecked((int)uint.MaxValue - int.MaxValue - (int)1)); – strategic.learner Jan 13 '19 at 20:18
  • Yes, ordering is preserved when you XOR the high bit (whether you do that with `v ^ int.MinValue`, `v + int.MinValue`, or `v - int.MaxValue - 1`. Ordering is not preserved in the suggestion that ckuri made which was "just cast to int" with no subtraction, no conversion of ranges. He was specifically talking about `uint.MaxValue` mapping to `-1` (read his comment again). Casting and also flipping the high bit does map `uint.MaxValue` to `int.MaxValue` just like you are thinking. – Ben Voigt Jan 13 '19 at 20:22
  • `int` constant is implicitly convertible to `uint` type, when it is in `uint` range (`int.MaxValue` is in `uint` range). [A *constant_expression* of type `int` can be converted to type `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the *constant_expression* is within the range of the destination type.](https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#implicit-constant-expression-conversions) – user4003407 Jan 13 '19 at 20:31

1 Answers1

2

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.

user4003407
  • 21,204
  • 4
  • 50
  • 60