В C операторы сдвига ( <<
, >>
) являются арифметическими или логическими?
c
binary
bit-manipulation
bit-shift
littlebyte
источник
источник
Ответы:
Согласно K&R 2nd edition результаты зависят от реализации для сдвига вправо знаковых значений.
Википедия говорит, что C / C ++ «обычно» реализует арифметический сдвиг в знаковых значениях.
В основном вам нужно либо протестировать свой компилятор, либо не полагаться на него. Моя справка VS2008 для текущего компилятора MS C ++ говорит, что их компилятор выполняет арифметический сдвиг.
источник
При сдвиге влево нет разницы между арифметическим и логическим сдвигом. При сдвиге вправо тип сдвига зависит от типа сдвигаемого значения.
(Для читателей, незнакомых с разницей, «логический» сдвиг вправо на 1 бит сдвигает все биты вправо и заполняет крайний левый бит нулем. При «арифметическом» сдвиге исходное значение остается в крайнем левом бите. . Разница становится важной при работе с отрицательными числами.)
При сдвиге беззнакового значения оператор >> в C является логическим сдвигом. При сдвиге значения со знаком оператор >> выполняет арифметический сдвиг.
Например, для 32-битной машины:
источник
TL; DR
Считайте
i
иn
левым и правым операндами соответственно оператора сдвига; типi
после целочисленного продвижения бытьT
. Предполагая,n
что находится в[0, sizeof(i) * CHAR_BIT)
- в противном случае undefined - мы имеем следующие случаи:† большинство компиляторов реализуют это как арифметический сдвиг
‡ undefined, если значение превышает тип результата T; продвинутый тип i
перевод
Во-первых, разница между логическим и арифметическим сдвигами с математической точки зрения, не беспокоясь о размере типа данных. Логические сдвиги всегда заполняют отброшенные биты нулями, в то время как арифметический сдвиг заполняет их нулями только для сдвига влево, но для сдвига вправо он копирует MSB, тем самым сохраняя знак операнда (предполагая кодирование с дополнением до двух для отрицательных значений).
Другими словами, логический сдвиг рассматривает сдвинутый операнд как просто поток битов и перемещает их, не заботясь о знаке результирующего значения. Арифметический сдвиг рассматривает его как (подписанное) число и сохраняет знак при выполнении сдвигов.
Левый арифметический сдвиг числа X на n эквивалентен умножению X на 2 n и, таким образом, эквивалентен логическому сдвигу влево; логический сдвиг также даст тот же результат, поскольку MSB все равно отваливается от конца, и сохранять нечего.
Правый арифметический сдвиг числа X на n эквивалентен целочисленному делению X на 2 n, ТОЛЬКО если X неотрицательно! Целочисленное деление - это не что иное, как математическое деление и округление до 0 ( усечение ).
Для отрицательных чисел, представленных дополнительным кодированием до двух, сдвиг вправо на n битов имеет эффект математического деления на 2 n и округления в сторону -∞ ( пол ); таким образом, сдвиг вправо отличается для неотрицательных и отрицательных значений.
где
÷
- математическое деление,/
- целочисленное деление. Давайте посмотрим на пример:Как отметил Гай Стил , это несоответствие привело к ошибкам в более чем одном компиляторе . Здесь неотрицательные (математические) могут быть сопоставлены с неотрицательными значениями без знака и со знаком (C); оба обрабатываются одинаково, и их сдвиг вправо выполняется целочисленным делением.
Таким образом, логика и арифметика эквивалентны при сдвиге влево и для неотрицательных значений при сдвиге вправо; они отличаются смещением отрицательных значений вправо.
Типы операндов и результатов
Стандарт C99 §6.5.7 :
В приведенном выше фрагменте оба операнда становятся
int
(из-за целочисленного продвижения); еслиE2
было отрицательным, илиE2 ≥ sizeof(int) * CHAR_BIT
тогда операция не определена. Это потому, что сдвиг большего количества бит, чем доступно, наверняка приведет к переполнению. Если бы онR
был объявлен какshort
,int
результат операции сдвига неявно преобразовывался бы вshort
; сужающее преобразование, которое может привести к поведению, определяемому реализацией, если значение не может быть представлено в целевом типе.Левый "шифт
Поскольку сдвиги влево одинаковы для обоих, освободившиеся биты просто заполняются нулями. Затем он заявляет, что как для беззнакового, так и для подписанного типов это арифметический сдвиг. Я интерпретирую это как арифметический сдвиг, поскольку логические сдвиги не заботятся о значении, представленном битами, он просто смотрит на него как на поток битов; но стандарт говорит не в терминах битов, а в терминах значения, полученного произведением E1 на 2 E2 .
Предостережение заключается в том, что для подписанных типов значение должно быть неотрицательным, а результирующее значение должно быть представлено в типе результата. В противном случае операция не определена. Тип результата будет типом E1 после применения интегрального продвижения, а не типом назначения (переменная, которая будет содержать результат). Результирующее значение неявно преобразуется в целевой тип; если он не может быть представлен в этом типе, то преобразование определяется реализацией (C99 §6.3.1.3 / 3).
Если E1 - знаковый тип с отрицательным значением, то поведение сдвига влево не определено. Это простой путь к неопределенному поведению, которое можно легко упустить из виду.
Сдвиг вправо
Сдвиг вправо для неотрицательных значений без знака и со знаком довольно прост; пустые биты заполняются нулями. Для отрицательных значений со знаком результат сдвига вправо определяется реализацией. Тем не менее, большинство реализаций, таких как GCC и Visual C ++, реализуют сдвиг вправо как арифметический сдвиг, сохраняя бит знака.
Вывод
В отличие от Java, в которой есть специальный оператор
>>>
для логического сдвига, кроме обычного>>
и<<
, в C и C ++ есть только арифметический сдвиг, при этом некоторые области остаются неопределенными и определяются реализацией. Причина, по которой я считаю их арифметическими, связана со стандартной формулировкой операции математически, а не с обработкой смещенного операнда как потока битов; это, возможно, причина, по которой он оставляет эти области не определенными для реализации, а не просто определяет все случаи как логические сдвиги.источник
-Inf
как отрицательных, так и положительных чисел. Округление положительного числа в сторону 0 - это частный случай округления в сторону-Inf
. При усечении вы всегда отбрасываете положительно взвешенные значения, поэтому вы вычитаете из иначе точного результата.Что касается типа сдвига, который вы получаете, важен тип сдвигаемого значения. Классический источник ошибок - это когда вы сдвигаете буквальное значение, например, чтобы скрыть биты. Например, если вы хотите удалить самый левый бит целого числа без знака, то вы можете попробовать это в качестве маски:
К сожалению, это доставит вам неприятности, потому что в маске будут установлены все биты, потому что сдвигаемое значение (~ 0) подписано, поэтому выполняется арифметический сдвиг. Вместо этого вы захотите принудительно выполнить логический сдвиг, явно объявив значение как беззнаковое, то есть сделав что-то вроде этого:
источник
Вот функции, гарантирующие логический сдвиг вправо и арифметический сдвиг вправо для int в C:
источник
Когда вы это делаете - сдвиг влево на 1 умножается на 2 - сдвиг вправо на 1 вы делите на 2
источник
Я поискал это в Википедии , и они сказали следующее:
Похоже, это зависит от вашего компилятора. Также в этой статье обратите внимание, что сдвиг влево одинаков для арифметики и логики. Я бы порекомендовал сделать простой тест с несколькими знаковыми и беззнаковыми числами на границе (конечно, с набором старших битов) и посмотреть, каков результат на вашем компиляторе. Я также рекомендовал бы избегать зависимости от того, является ли он одним или другим, поскольку кажется, что у С нет стандарта, по крайней мере, если это разумно и возможно избежать такой зависимости.
источник
Левый "шифт
<<
Это как-то легко, и всякий раз, когда вы используете оператор сдвига, это всегда побитовая операция, поэтому мы не можем использовать ее с операциями double и float. Каждый раз, когда мы оставляем сдвиг на один ноль, он всегда добавляется к младшему значащему биту (
LSB
).Но при сдвиге вправо
>>
мы должны следовать еще одному правилу, и это правило называется «копирование знакового бита». Значение «знакового копирования битов» означает, что если старший значащий бит (MSB
) установлен, то после правого сдвига сноваMSB
будет установлен, если он был сброшен, то он снова сброшен, означает, что если предыдущее значение было нулевым, то после повторного сдвига, бит равен нулю, если предыдущий бит был единицей, то после сдвига он снова равен единице. Это правило не действует для сдвига влево.Самый важный пример со сдвигом вправо, если вы сдвинете любое отрицательное число на сдвиг вправо, затем после некоторого сдвига значение, наконец, достигнет нуля, а затем после этого, если сдвиньте это -1 любое количество раз, значение останется прежним. Пожалуйста, проверьте.
источник
НКУобычно использует логические сдвиги для переменных без знака и сдвиги влево для переменных со знаком. Арифметический сдвиг вправо действительно важен, потому что он расширяет переменную.
НКУ будет использовать это, когда это применимо, как, вероятно, будут делать другие компиляторы.
источник
GCC делает
для -ve -> Арифметический сдвиг
For + ve -> Logical Shift
источник
По мнению многих с составители:
<<
это арифметический сдвиг влево или побитовый сдвиг влево.>>
- арифметический сдвиг вправо или побитовый сдвиг вправо.источник
>>
Арифметический или побитовый (логический)?» Вы ответили ">>
арифметически или поразрядно" Это не отвечает на вопрос.<<
и>>
операторы логические, а не арифметические