Подтвердить что ты не робот

Почему -1 >> 1 - -1? И 1 >> 1 - 0!

У меня есть следующий код:

std::cout << (-10 >> 1) << std::endl;
std::cout << (-9 >> 1) << std::endl;
std::cout << (-8 >> 1) << std::endl;
std::cout << (-7 >> 1) << std::endl;
std::cout << (-6 >> 1) << std::endl;
std::cout << (-5 >> 1) << std::endl;
std::cout << (-4 >> 1) << std::endl;
std::cout << (-3 >> 1) << std::endl;
std::cout << (-2 >> 1) << std::endl;
std::cout << (-1 >> 1) << std::endl;

Результат:

-5
-5
-4
-4
-3
-3
-2
-2
-1
-1

Но почему?

-1 равен 1111 1111 (для 1 байт), -1 >> 1 должен быть: 1011 1111 и это не -1 или 0! (знаковый бит не сдвигается, я знаю)

Может кто-нибудь объяснить мне, как это работает?

4b9b3361

Ответ 1

Стандарт 5.8/3 (Операторы сдвига):

Значение E1 → E2 равно E1 сдвинутые вправо позиции E2. Если E1 имеет неподписанный тип, или если E1 имеет подписанный тип и неотрицательное значение, значение результата интегральная часть частного E1 делится на величину 2, поднятую до мощность E2. Если E1 имеет подписанный тип и отрицательное значение, в результате Значение определяется реализацией.

Итак, на вопрос "почему?", стандартный ответ: почему бы и нет.

Ответ 2

Правильное смещение отрицательного числа определяется реализацией.

Реализации, которые сдвигаются в бит расширенного знака до самого левого бита, работают, как вы сообщали.

Что касается того, почему это делается так, это потому, что правое смещение может использоваться для разделения на степень 2 с округлением округления к отрицательной бесконечности (например, floor()):

(-8 >> 2) == -2
(-9 >> 2) == -3
(-10 >> 2) == -3
(-11 >> 2) == -3
(-12 >> 2) == -3

Смотрите этот вопрос.

Ответ 3

-1 → 1 должен быть: 1011 1111

Если бы это было так, то -10 → 1 было бы 10111011 == -69 в двух дополнениях. Не очень полезный результат!

Хотя поведение языка undefined (поэтому на результат, полезный или на что-то другое нельзя полагаться), общее поведение (и проявленное в этом случае) заключается в выполнении "расширения знака".

Неверно, что "знаковый бит не сдвигается", он сдвигается, а освобожденный бит заполняется значением, равным знакомувому биту. Это поведение сохраняет знак и обеспечивает "ожидаемую" операцию "разделить на два", которую вы наблюдали для всех значений, кроме -1. Для отрицательных значений -1 - "конечное значение" правого сдвига, так как нуль для положительных чисел. То есть правый сдвиг отрицательного числа стремится к -1, а положительное число стремится к нулю.

Ответ 4

В общем, сдвиги вправо определяются как реализуемые как "арифметические" или "логические". Разница заключается в том, что при логическом сдвиге справа самый левый бит всегда равен нулю. С арифметическим сдвигом вправо самый левый бит является копией предыдущего значения.

Например, допустим, что значение составляет всего 8 бит, чтобы упростить отслеживание. Тогда:

Логическое:

0111 1111 >> 1 = 0011 1111
1111 1111 >> 1 = 0111 1111
1111 1110 >> 1 = 0111 1111

Арифметика:

0111 1111 >> 1 = 0011 1111
1111 1111 >> 1 = 1111 1111
1111 1110 >> 1 = 1111 1111

Арифметический правый сдвиг эквивалентен делению на 2 и округлению к отрицательной бесконечности.

В С++, является ли оператор с правом сдвигом логическим или арифметическим является специфичным для реализации. то есть каждый автор-компилятор может решить для себя, вероятно, исходя из того, что легче сделать, создавая архитектуру компьютера, над которым он работает.

Ваше утверждение о том, что бит знака не сдвинуто, неверно. Битовый знак сдвигается так же, как и все остальные биты. Вопрос только в том, что его заменяет.

Java имеет два разных оператора с правом сдвига: → является арифметическим и → > логичным.