Арифметическое смещение битов на целых числах со знаком

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

Для простоты, скажем, мы работаем в пределах одного байта (8 бит):

x = 1101.0101
MSB[ 1101.0101 ]LSB

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

А также >> сместится в сторону LSB (в моем случае вправо) и заполнит "пустые" биты битом MS

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

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 !!!!!) 

Зачем?

4 ответа

Решение

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

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

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


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

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


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

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

для "<<"

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

и для ">>"

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

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

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

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

Начиная с C++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.

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

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

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

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

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

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

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

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

х = х >> 31;

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

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

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

тогда окончательный х равен -32768

На моем i7:

uint64_t:

0xffffffffffffffff >> 0 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 1 is 0b0111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 2 is 0b0011111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 3 is 0b0001111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 4 is 0b0000111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 62 is 0b0000000000000000000000000000000000000000000000000000000000000011
0xffffffffffffffff >> 63 is 0b0000000000000000000000000000000000000000000000000000000000000001
0xffffffffffffffff >> 64 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 65 is 0b0111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 66 is 0b0011111111111111111111111111111111111111111111111111111111111111

int64_t -1

0xffffffffffffffff >> 0 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 1 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 2 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 3 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 4 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 62 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 63 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 64 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 65 is 0b1111111111111111111111111111111111111111111111111111111111111111
0xffffffffffffffff >> 66 is 0b1111111111111111111111111111111111111111111111111111111111111111

int64_t 2^63-1

0x7fffffffffffffff >> 0 is 0b0111111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 1 is 0b0011111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 2 is 0b0001111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 3 is 0b0000111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 4 is 0b0000011111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 62 is 0b0000000000000000000000000000000000000000000000000000000000000001
0x7fffffffffffffff >> 63 is 0b0000000000000000000000000000000000000000000000000000000000000000
0x7fffffffffffffff >> 64 is 0b0111111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 65 is 0b0011111111111111111111111111111111111111111111111111111111111111
0x7fffffffffffffff >> 66 is 0b0001111111111111111111111111111111111111111111111111111111111111
Другие вопросы по тегам