Суммируется ли отрицательное целое число с большим целым без знака, повышенным до целого без знака?

После того, как мне посоветовали прочитать "C++ Primer 5 ed" Стэнли Б. Липмана ", я не понимаю этого:

Страница 66. "Выражения, включающие неподписанные типы"

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // prints -84
std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264

Он сказал:

Во втором выражении значение int -42 преобразуется в unsigned до того, как добавление выполнено. Преобразование отрицательного числа в неподписанное ведет себя точно так же, как если бы мы пытались присвоить это отрицательное значение неподписанному объекту. Значение "оборачивается", как описано выше.

Но если я сделаю что-то вроде этого:

unsigned u = 42;
int i = -10;
std::cout << u + i << std::endl; // Why the result is 32?

Как видите, -10 не конвертируется в unsigned int. Значит ли это сравнение происходит до продвижения signed integer на unsigned integer?

Ответы

Ответ 1

-10 преобразуется в целое число без знака с очень большим значением. Причина, по которой вы получаете небольшое число, заключается в том, что дополнение оборачивает вас обратно. С 32-разрядными целыми числами без знака -10 совпадает с 4294967286. Когда вы добавляете 42 к этому, вы получаете 4294967328, но максимальное значение 4294967296, поэтому мы должны взять 4294967328 по модулю 4294967296 и мы получим 32.

Ответ 2

Ну, я думаю, что это исключение из "двух ошибок, которые не делают права" :)

Происходит то, что на самом деле под капотом есть две обертки (без знака переполнения), и конечный результат оказывается математически правильным.

  • Во-первых, i конвертируется в unsigned, и в соответствии с поведением обтекания значением является std::numeric_limits<unsigned>::max() - 9.

  • Когда это значение суммируется с u математическим результатом будет std::numeric_limits<unsigned>::max() - 9 + 42 == std::numeric_limits<unsigned>::max() + 33 что является переполнением, и мы сделай еще один поворот. Итоговый результат - 32.


Как общее правило в арифметическом выражении, если у вас есть только переполнения без знака (независимо от того, сколько) и если конечный математический результат представлен в типе данных выражения, то значение выражения будет математически корректным. Это является следствием того факта, что целые числа без знака в C++ подчиняются законам арифметики по модулю 2 n (см. Ниже).


Важное замечание. Согласно C++ арифметика без знака не переполняется:

§6.9.1 Фундаментальные типы [basic.fundamental]

  1. Целые числа без знака должны подчиняться законам арифметики по модулю 2 n, где n - количество битов в представлении значения этого конкретного размера целого числа 49

49) Это подразумевает, что арифметика без знака не переполняется, потому что результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю на число, которое на единицу больше наибольшего значения, которое может быть представлено результирующим целочисленным типом без знака.

Однако я оставлю в своем ответе "переполнение", чтобы выразить значения, которые нельзя представить в обычной арифметике.

Также то, что мы в разговорной речи называем "обтекание", на самом деле является просто арифметической природой по модулю целых чисел без знака. Однако я буду использовать "обтекание" также потому, что это легче понять.

Ответ 3

i фактически повышен до unsigned int.

Целые числа без знака в C и C++ реализуют арифметику в ℤ /2 n ℤ, где n - количество битов в целочисленном типе без знака. Таким образом, мы получаем

[42] + [-10] ≡ [42] + [2 n - 10] ≡ [2 n + 32] ≡ [32],

с [x] обозначает класс эквивалентности x в ℤ /2 n ℤ.

Конечно, промежуточный этап выбора только неотрицательных представителей каждого класса эквивалентности, хотя он и происходит формально, не является необходимым для объяснения результата; непосредственный

[42] + [-10] ≡ [32]

также будет правильно.

Ответ 4

"Во втором выражении значение int -42 преобразуется в unsigned до того, как добавление выполнено"

Да, это правда

unsigned u = 42;
int i = -10;
std::cout << u + i << std::endl; // Why the result is 32?

Предположим, что мы находимся в 32 битах (что ничего не меняет в 64b, это просто для объяснения), это вычисляется как 42u + ((unsigned) -10) так что 42u + 4294967286u и результат 4294967328u, усеченный в 32 бита, так что 32. Все было сделано в неподписанном

Ответ 5

Это часть того, что замечательно в представлении 2 дополнения. Процессор не знает и не заботится о том, что число подписано или не подписано, операции одинаковы. В обоих случаях расчет верен. Это только то, как двоичное число интерпретируется после факта, при печати, что на самом деле имеет значение (могут быть и другие случаи, как с операторами сравнения)

-10 in 32BIT binary is FFFFFFF6
42 IN 32bit BINARY is  0000002A

Сложив их вместе, для процессора не имеет значения, если они подписаны или не подписаны, результат: 100000020. В 32-битном случае 1 в начале будет помещен в регистр переполнения, а в c++ просто исчезнет. В результате вы получите 0x20, то есть 32.

В первом случае это в основном то же самое:

-42 in 32BIT binary is FFFFFFD6
10 IN 32bit binary is 0000000A

Добавьте их вместе и получите FFFFFFE0

FFFFFFE0 в виде целого числа со знаком - -32 (десятичный). Расчет верен! Но поскольку он печатается как неподписанный, он отображается как 4294967264. Это о интерпретации результата.