Арифметический бит-сдвиг по целому знаку со знаком

Я пытаюсь понять, как точно работают арифметические операторы смены битов в C и как это повлияет на подписанные 32-битные целые числа.

Чтобы сделать вещи простыми, скажем, мы работаем в течение одного байта (8 бит):

x = 1101.0101
MSB[ 1101.0101 ]LSB

Чтение других сообщений в Qaru и некоторых веб-сайтах, я обнаружил, что: << будет сдвигаться в сторону MSB (слева, в моем случае) и заполнить "пустые" бит LSB с помощью 0s.

И >> будет сдвигаться в сторону LSB (справа, в моем случае) и заполнить "пустые" биты бит MS

Итак, x = x << 7 приведет к перемещению LSB в MSB и настройке всего на 0s.

1000.0000

Теперь, скажем, я бы >> 7, последний результат. Это приведет к [0000.0010]? Я прав?

Я прав о своих предположениях относительно операторов сдвига?

Я только что проверил на своей машине, **

int x = 1;   //000000000......01

x = x << 31; //100000000......00

x = x >> 31; //111111111......11 (Everything is filled with 1s !!!!!) 

Почему?

Ответ 1

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

Если ваши 8 бит предназначены для представления подписанного 8-битного значения (поскольку вы говорите о "подписанном 32-битном целочисленном" перед переключением на 8-битные примеры), то у вас есть отрицательное число. Смещение его права может заполнить "пустые" биты исходным MSB (т.е. Выполнить расширение знака) или может сдвигаться в нулях в зависимости от платформы и/или компилятора.

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


Левый сдвиг, если число либо начинается отрицательно, либо операция сдвига сдвигает 1 либо на знаковый бит, либо превышает его, имеет поведение undefined (как и большинство операций с подписанными значениями, которые вызывают переполнение).

(Undefined поведение означает, что все может произойти.)


Те же операции с неподписанными значениями хорошо определены в обоих случаях: "пустые" биты будут заполнены 0.

Ответ 2

Операции побитового сдвига не определены для отрицательных значений

для '<'

6.5.7/4 [...] Если E1 имеет подписанный тип и неотрицательное значение, а E1 × 2 E2 представляется в типе результата, то это результирующее значение; в противном случае поведение undefined.

и для ' → '

6.5.7/5 [...] Если E1 имеет подписанный тип и отрицательное значение, результирующее значение определяется реализацией.

Это пустая трата времени, чтобы изучить поведение этих операций на подписанных числах в конкретной реализации, потому что у вас нет гарантии, что она будет работать одинаково в любой другой реализации (реализация - это, например, ваш компилятор на вашем компьютер с определенными параметрами commad-line).

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

Учитывая только неотрицательные значения, эффект сдвига влево на 1 (expression << 1) совпадает с мультиплицированием выражения на 2 (при условии, что выражение * 2 не переполняется), а эффект правого сдвига на 1 (expression >> 1) совпадает с делением на 2.

Ответ 3

Как говорят другие, сдвиг отрицательного значения определяется реализацией.

Большинство реализаций обрабатывают подписанный сдвиг вправо как пол (x/2 N) путем заполнения сдвига в битах с использованием бита знака. Это очень удобно на практике, так как эта операция настолько распространена. С другой стороны, если вы сдвинете правое целое без знака, смещенные в битах будут обнулены.

Если смотреть со стороны машины, большинство реализаций имеют два типа команд сдвига:

  • "Арифметический" сдвиг вправо (часто имеющий мнемонический ASR или SRA), который работает, как я объяснил.

  • "Логический" сдвиг вправо (oftem с Mnemonic LSR или SRL или SR), который работает так, как вы ожидаете.

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

Ответ 4

В 32-битном компиляторе

x = x → 31;

здесь x - целое число со знаком, так что 32-й бит является битом знака.

Конечное значение x составляет 100000... 000. и 32-й бит указывают значение -ive.

здесь значение x реализует 1 комплимент.

то окончательный x равен -32768

Ответ 5

Начиная с С++ 20 операторы побитового сдвига для целых чисел со знаком хорошо определены.

Сдвиг влево a<<b эквивалентен модулю a*2^b 2^N где N - количество битов в результирующем типе. В частности, 1<<31 на самом деле является наименьшим значением int.

Сдвиг вправо a>>b эквивалентен a/2^b, округленному вниз (то есть к отрицательной бесконечности). Например, -1>>10 == -1.

Для получения более подробной информации смотрите https://en.cppreference.com/w/cpp/language/operator_arithmetic.

(для более старых стандартов см. ответ Мэтью Слэттери)