Пора вернуться к третьей строке разминки. И снова всё решает дополнительный код из урока 5.2.
У отрицательного числа в дополнительном коде слева – бесконечная цепочка единиц (мы это уже видели в уроке 6.1, когда разбирались с ~). При сдвиге вправо биты едут вниз, и слева что-то должно въезжать. Если въедут нули, знак потеряется: −7 внезапно станет большим положительным числом. Поэтому используется арифметический сдвиг – слева въезжает копия знакового бита. У отрицательного числа это единицы, у положительного нули, и знак сохраняется сам собой.
Проверим -7 >> 1:
…1111001 (−7)
>> 1
= …1111100 (−4)
Число осталось отрицательным, и получилось −4. Тот же ответ даёт -7 // 2, потому что // округляет вниз, в сторону минус бесконечности, а не «к нулю»: −3.5, округлённое вниз, – это −4.
Соберём ответ на вопрос «почему −4, а не −3» в одну фразу, её стоит запомнить дословно: слева въезжает копия знакового бита, поэтому число остаётся отрицательным, а уехавший младший бит просто теряется – получается деление нацело с округлением вниз, ровно как у //.
Значит, правило x >> k = x // 2**k работает и для отрицательных чисел без исключений – именно потому, что и сдвиг, и // округляют в одну и ту же сторону. А вот int(-7 / 2) даст −3: обычное деление с отбрасыванием дробной части – это уже другая операция.
Отсюда же ещё одно следствие: сдвигая отрицательное число вправо сколько угодно долго, вы получите не 0, а −1 (-7 >> 10 → −1). Бесконечная цепочка единиц никуда не девается.
Всё это можно посмотреть по шагам в виджете выше: переключите операцию на «>> вправо», включите «знаковое» и введите −7. Жёлтые разряды слева – то, что въехало, пунктирные ячейки справа – выпавшие биты, то есть остаток от деления.