Подпись номера дополнений 2 - это просто вопрос того, как вы интерпретируете число. Представьте себе 3 бит номер:
000
001
010
011
100
101
110
111
Если вы думаете о 000
как ноль и число, поскольку они являются естественными для человека, вы бы их интерпретировать так:
000: 0
001: 1
010: 2
011: 3
100: 4
101: 5
110: 6
111: 7
Это называется «целое число без знака ». Вы видите все как число, большее или равное нулю.
Теперь, если вы хотите, чтобы некоторые цифры были отрицательными? Ну, 2-й комплект приходит на помощь. 2 известен большинству людей как просто формула, но на самом деле это просто конгруэнтность по модулю 2^n, где n - количество бит в вашем номере.
Позвольте мне дать вам несколько примеров конгруэнтности:
2 = 5 = 8 = -1 = -4 module 3
-2 = 6 = 14 module 8
Теперь, для удобства, скажем, вы решили иметь самый левый бит числа как его знак. Итак, вы хотите иметь:
000: 0
001: positive
010: positive
011: positive
100: negative
101: negative
110: negative
111: negative
Просмотр ваших чисел, сравнимых по модулю 2^3 (= 8), вы знаете, что:
4 = -4
5 = -3
6 = -2
7 = -1
Таким образом, вы просматривать номера, как:
000: 0
001: 1
010: 2
011: 3
100: -4
101: -3
110: -2
111: -1
Как вы можете видеть, фактические биты для -3 и 5 (например) одинаковы (если число имеет 3 бита). Поэтому запись x = -3
или x = 5
дает вам тот же результат.
Интерпретация чисел конгруэнтно по модулю 2^n имеет другие преимущества. Если вы суммируете 2 числа, один отрицательный и один положительный, на бумаге может случиться, что у вас есть перенос, который будет выброшен, но результат все же правильный. Зачем? Этот перенос был равен 2^п, что соответствует 0 по модулю 2^п! Разве это не удобно?
Переполнение также является другим случаем сравнения. В нашем примере, если вы суммируете два беззнаковых номера 5 и 6, вы получаете 3, что на самом деле 11.
Итак, почему вы используете подписанные и неподписанные? Для CPU на самом деле очень мало различий. Для вы однако:
- Если число имеет п бит, без знака представляет собой число от 0 до 2^п-1
- Если число имеет п битов, подписанная представляет число от -2^(n-1) до 2^(n-1) -1
Так, например, если вы присваиваете -1 значению без знака, это то же самое, что и присвоение ему 2^n-1.
Согласно вашему примеру, это именно то, что вы делаете. вы назначаете -3 для uint8_t, что является незаконным, но, что касается процессора, вы назначаете ему 253. Тогда все остальные операции одинаковы для обоих типов, и вы получите тот же результат.
Существует, однако, пункт, который ваш пример пропускает. оператор >>
на подписанном числе расширяет знак при сдвиге. Поскольку результат обеих ваших операций равен 9, вы не заметите этого. Если у вас не было +15, у вас было бы -6 в i
и 250 в u
, которое затем >> 2
привело бы к -2
в i
(если напечатано с% u, 254) и 62 в u
.(Смотрите комментарий Peter Корд ниже на несколько тонкостей)
Чтобы лучше это понять, принять этот пример:
(signed)101011 (-21) >> 3 ----> 111101 (-3)
(unsigned)101011 (43) >> 3 ----> 000101 (5)
Если вы заметили, пол (-21/8) на самом деле -3 и пол (43/8) равно 5. Однако -3 и 5 не равны (и не соответствуют по модулю 64 (64 из-за наличия 6 бит))
Ну, это будет зависеть от того, нужна ли вам целая переменная принимать отрицательные значения ... –
'printf («% u », i)' is wrong (неопределенное поведение даже, IIRC). – delnan
Это довольно старая и ценная статья, написанная Скоттом Мейерсом давно: http://www.aristeia.com/Papers/C++ReportColumns/sep95.pdf – AraK