Почему знак отличается после вычитания неподписанных и подписанных?
unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;
Почему значение c
положительным, но e
отрицательным?
Ответы
Ответ 1
Начнем с анализа результата t - d
.
t
является unsigned int
а d
является int
, поэтому для выполнения арифметики на них значение d
преобразуется в unsigned int
(C++ правила говорят, что unsigned получает предпочтение здесь). Таким образом, мы получаем 10u - 16u
, который (предполагая 32-битный int
) обтекает 4294967290u
.
Затем это значение преобразуется в float
в первом объявлении и в int
во втором.
Предполагая, что типичная реализация float
(32-битная 1e38
IEEE), ее наивысшее представимое значение составляет примерно 1e38
, поэтому 4294967290u
находится в пределах этого диапазона. Будут ошибки округления, но преобразование в float не будет переполняться.
Для int
ситуация другая. 4294967290u
слишком большой, чтобы вписаться в int
, поэтому происходит обход, и мы возвращаемся к значению -6
. Обратите внимание, что такой обертку не гарантируется стандартом: результирующее значение в этом случае определяется реализацией (1) что означает, что для компилятора это значение результата, но оно должно быть документировано.
(1) C++ 17 (N4659), [conv.integral] 7.8/3:
Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения; в противном случае значение определяется реализацией.
Ответ 2
Во-первых, вы должны понимать "обычные арифметические преобразования" (эта ссылка для C, но правила в C++ одинаковы). В C++, если вы выполняете арифметику со смешанными типами (вам следует избегать, если это возможно, кстати), существует набор правил, которые определяют, в каком типе выполняется вычисление.
В вашем случае вы вычитаете подписанный int из unsigned int. Правила продвижения говорят, что фактический расчет выполняется с использованием unsigned int
.
Таким образом, ваш расчет равен 10 - 16
в unsigned int арифметике. Беззнаковая арифметика является по модулю арифметикой, что означает, что она обертывается. Итак, предполагая ваш типичный 32-битный int, результат этого расчета составляет 2 ^ 32 - 6.
Это одинаково для обеих линий. Обратите внимание, что вычитание полностью не зависит от назначения; тип с левой стороны абсолютно не влияет на то, как происходит расчет. Общей ошибкой начинающих считается мысль о том, что тип на левой стороне каким-то образом влияет на расчет; но float f = 5/6
равно нулю, потому что деление по-прежнему использует целочисленную арифметику.
Следовательно, разница заключается в том, что происходит во время назначения. Результат вычитания неявно преобразуется в float
в одном случае, а int
в другой.
Преобразование в float пытается найти ближайшее значение к фактическому, которое может представлять тип. Это будет очень большое значение; не совсем тот, который дал исходное вычитание.
Преобразование в int говорит, что если значение вписывается в диапазон int, значение не изменится. Но 2 ^ 32 - 6 намного больше, чем 2 ^ 31 - 1, что может содержать 32-битный int, поэтому вы получаете другую часть правила преобразования, в которой говорится, что результирующее значение определяется реализацией. Это термин в стандарте, который означает, что "разные компиляторы могут делать разные вещи, но им приходится документировать, что они делают".
Для всех практических целей все компиляторы, с которыми вы, вероятно, столкнетесь, скажете, что бит-шаблон остается неизменным и просто интерпретируется как подписанный. Из-за того, как работает арифметика с двумя дополнениями (так, что почти все компьютеры представляют отрицательные числа), результатом является -6, который вы ожидаете от вычисления.
Но все это очень длинный путь повторения первой точки, которая "не выполняет смешанную арифметику типа". Сначала введите типы, явно, в типы, которые, как вы знаете, сделают правильные вещи.