Почему вычитание int.MaxValue из uint отличается от вычитания переменной типа int из uint?

По крайней мере, на первый взгляд, это не вопрос переполнения int или uint. Я понимаю, что значения перекрываются только ~ наполовину, а диапазоны равны. Собственно, я на это и рассчитываю.

Общие сведения. У меня есть хеш-алгоритм DotNet CRC, который возвращает uint, и мне нужно преобразовать его для хранения в sql (в котором отсутствует uint). Это преобразование всегда должно быть возможным, поскольку общий диапазон обоих равен, даже если начальная и конечная точки различны. Нет никаких опасений по поводу обратного преобразования. Я отладил это как DotNet 4.0 и 4.6.1 с теми же результатами.

Я в замешательстве:

В приведенных ниже примерах кода:
intForSqlStorageOK выполняется успешно.
Но intForSqlStorageFail1 создает исключение среды выполнения.
Чем они отличаются?


{
    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;

}

Спасибо всем за комментарии! многому научился


person strategic.learner    schedule 13.01.2019    source источник
comment
Вам не нужно вручную преобразовывать входные диапазоны, просто выполните (int)yourUint. Если значение больше, чем int.MaxValue, оно автоматически станет отрицательным значением int, например. uint.MaxValue станет int -1.   -  person ckuri    schedule 13.01.2019
comment
@ckuri - все еще нелогично в моей голове - но это работает! Спасибо!   -  person strategic.learner    schedule 13.01.2019
comment
@ckuri: Если вам не нужно выполнять сравнения в SQL, в этом случае вам также необходимо выполнить XOR бита MSB/знака, чтобы сохранить порядок.   -  person Ben Voigt    schedule 13.01.2019
comment
Я дам вам кое-что для размышления, чтобы увидеть, приблизит ли это вас к пониманию. Что произойдет с выполнением вашего кода, если вы поместите const перед int intMaxValue = int.MaxValue; Почему вы думаете, что это происходит?   -  person mjwills    schedule 13.01.2019
comment
@mjwills - Что сделал код? ‹Дал 2 разных результата для того, что мне кажется одним и тем же кодом.› Что вы ожидали от него? ‹I ожидал, что оба будут вести себя одинаково› Почему вы ожидали, что он сделает это? ‹потому что я не понимаю разницы между выполнением математических операций над переменной int, хранящей значение int.MaxValue, и выполнением математических операций напрямую с/на int.MaxValue›   -  person strategic.learner    schedule 13.01.2019
comment
@mjwills - я думаю, что это ставит меня на путь поиска ответа сейчас - я не думал о int.MaxValue как о неизменном по своей сути, но да, да. И я видел упоминание const в других темах - спасибо!   -  person strategic.learner    schedule 13.01.2019
comment
@Ben Voigt - предложение ckuri, кажется, работает нормально, по крайней мере, в общей функции, которую я указал выше. Думаю, это потому, что я сначала конвертирую в int в С#, а затем пишу в Sql.   -  person strategic.learner    schedule 13.01.2019
comment
Он отлично работает, если вы не используете преобразованные значения для сравнения в SQL-запросах, только для преобразования обратно в uint. Если бы вы проводили сравнения в SQL, вы бы обнаружили, что (int)1 > (int)uint.MaxValue (неправильный ответ).   -  person Ben Voigt    schedule 13.01.2019
comment
@Ben Voight - до тех пор, пока каждое значение, передаваемое в sql, получает одинаковый коэффициент преобразования; они очень сопоставимы в sql. OTOH, если бы я использовал UNconverted uint в C# для прямого сравнения с преобразованным int Sql - тогда да, сравнение было бы недействительным.   -  person strategic.learner    schedule 13.01.2019
comment
@strategic.learner: Только для сравнения равенства. Порядок не будет сохранен простым приведением к типу int. Смотрите пример в моем комментарии.   -  person Ben Voigt    schedule 13.01.2019
comment
Кстати, теперь вы можете добавить int.MinValue вместо вычитания int.MaxValue и еще 1.   -  person Ben Voigt    schedule 13.01.2019
comment
@ Бен Войт - лол, наконец-то понял, к чему ты клонишь. 1) Мой вариант использования - это хэш-значение, поэтому порядок не имеет значения, только равенство. 2) Порядок IS сохраняется путем вычитания int.MaxValue - (int)1. Диапазон всегда смещается ровно наполовину. ((uint)0 => int.minvalue) (uint.maxvalue/2 => (int)0) (uint.MaxValue => int.MaxValue)   -  person strategic.learner    schedule 13.01.2019
comment
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));   -  person strategic.learner    schedule 13.01.2019
comment
Да, порядок сохраняется, когда вы выполняете XOR для старшего бита (независимо от того, делаете ли вы это с v ^ int.MinValue, v + int.MinValue или v - int.MaxValue - 1. Порядок не сохраняется в предложении, сделанном ckuri, которое было просто преобразовано в int без вычитания, без преобразования диапазонов. Он специально говорил о сопоставлении uint.MaxValue с -1 (снова прочитайте его комментарий).Приведение, а также переворачивание старшего бита сопоставляет uint.MaxValue с int.MaxValue, как вы думаете.   -  person Ben Voigt    schedule 13.01.2019
comment
Константа int неявно преобразуется в тип uint, когда она находится в диапазоне uint (int.MaxValue находится в диапазоне uint). A constant_expression типа int можно преобразовать в тип sbyte, byte, short, ushort, uint или ulong при условии, что значение constant_expression находится в пределах диапазона целевого типа.   -  person user4003407    schedule 13.01.2019


Ответы (1)


Согласно спецификации, int.MaxValue можно неявно преобразовать в тип uint, тогда как непостоянное выражение int нельзя, потому что во время компиляции компилятор не знает, находится ли значение выражения int в диапазоне типа uint или нет.

Если вы примените правила разрешения оператора, то увидите, что uIntToBeStoredInSql - intMaxValue - 1 интерпретируется как (long)uIntToBeStoredInSql - (long)intMaxValue - 1L с общим значением -2146892577L (которое находится в диапазоне типа int).

В то время как uIntToBeStoredInSql - int.MaxValue - 1 интерпретируется как uIntToBeStoredInSql - (uint)int.MaxValue - 1u с общим значением 2148074719u (которое не находится в диапазоне типа int) в контексте unchecked и исключением OverflowException в контексте checked.

person user4003407    schedule 13.01.2019