Почему -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
! (знаковый бит не сдвигается, я знаю)
Может кто-нибудь объяснить мне, как это работает?
Ответы
Ответ 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 имеет два разных оператора с правом сдвига: → является арифметическим и → > логичным.