Являются ли операторы сдвига (<<, >>) арифметическими или логическими в C?

В C, операторы сдвига (<<, >>) арифметические или логические?

Ответ 1

Согласно K & R 2nd edition, результаты зависят от реализации для сдвигов вправо сдвинутых значений.

Wikipedia говорит, что C/С++ 'обычно' реализует арифметический сдвиг на значениях со знаком.

В принципе вам нужно либо проверить свой компилятор, либо не полагаться на него. Моя помощь VS2008 для текущего компилятора MS С++ говорит, что их компилятор выполняет арифметический сдвиг.

Ответ 2

При смещении влево нет никакой разницы между арифметическим и логическим сдвигом. При смещении вправо тип сдвига зависит от типа смещаемого значения.

(В качестве фона для тех читателей, которые не знакомы с разницей, "логический" сдвиг вправо на 1 бит сдвигает все биты вправо и заполняет самый левый бит с 0. "Арифметический" сдвиг оставляет исходное значение в самый левый бит. Разница становится важной при работе с отрицательными числами.)

При смещении значения без знака оператор → в C является логическим сдвигом. При смещении знакового значения оператор → является арифметическим сдвигом.

Например, при использовании 32-битной машины:

signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);

Ответ 3

TL; DR

Рассмотрим i и n как левый и правый операнды соответственно оператора сдвига; тип i, после цельного продвижения, будет T. Предполагая, что n находится в [0, sizeof(i) * CHAR_BIT) - undefined в противном случае - мы имеем такие случаи:

| Direction  |   Type   | Value (i) | Result                   |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    < 0    | Implementation-defined†  |
| Left  (<<) | unsigned |    ≥ 0    | (i * 2ⁿ) % (T_MAX + 1)   |
| Left       | signed   |    ≥ 0    | (i * 2ⁿ) ‡               |
| Left       | signed   |    < 0    | Undefined                |

† большинство компиляторов реализуют это как арифметический сдвиг
‡ undefined, если значение переполняет тип результата T; продвинутый тип i


Перемена

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

Другими словами, логический сдвиг рассматривает сдвинутый операнд как просто поток бит и перемещает их, не беспокоясь о значении результирующего значения. Арифметический сдвиг смотрит на него как (подписанное) число и сохраняет знак при сдвиге.

Левый арифметический сдвиг числа X на n эквивалентен умножению X на 2 n и, таким образом, эквивалентно логическому сдвигу влево; логический сдвиг также даст тот же результат, поскольку MSB все равно падает с конца и там ничего не сохраняется.

Правильный арифметический сдвиг числа X на n эквивалентен целочисленному делению X на 2 n ТОЛЬКО, если X неотрицательно! Целочисленное деление - это не что иное, как математическое деление и round в направлении 0 (trunc).

Для отрицательных чисел, представленных двумя кодовыми дополнениями, смещение справа на n бит имеет эффект математического деления на 2 n и округления в сторону -∞ (floor); таким образом, смещение вправо отличается от неотрицательных и отрицательных значений.

для X ≥ 0, X → n = X/2 n= trunc (X ÷ 2 n)

для X < 0, X → n = пол (X ÷ 2 n)

где ÷ - математическое деление, / - целочисленное деление. Рассмотрим пример:

37) 10= 100101) 2

37 ÷ 2 = 18,5

37/2 = 18 (округление 18,5 к 0) = 10010) 2 [результат арифметического сдвига вправо]

-37) 10= 11011011) 2 (с учетом двухкомпонентного 8-битного представления)

-37 ÷ 2 = -18,5

-37/2 = -18 (округление 18,5 к 0) = 11101110) 2 [НЕ результат арифметического сдвига вправо]

-37 → 1 = -19 (округление от 18,5 до -∞) = 11101101) 2 [результат арифметического сдвига вправо]

Как Guy Steele отметил, это несоответствие привело к ошибок в нескольких компиляторах. Здесь неотрицательный (математический) может быть отображен на неподписанные и подписанные неотрицательные значения (C); оба обрабатываются одинаково, а их правое смещение выполняется целым делением.

Таким образом, логика и арифметика эквивалентны в сдвиге слева и для неотрицательных значений при правом сдвиге; это при правильном смещении отрицательных значений, которые они отличаются.

Операнд и типы результатов

Стандарт C99 §6.5.7:

Каждый из операндов должен иметь целые типы.

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

short E1 = 1, E2 = 3;
int R = E1 << E2;

В приведенном выше фрагменте оба операнда становятся int (из-за цельного продвижения); если E2 был отрицательным или E2 ≥ sizeof(int) * CHAR_BIT, то операция undefined. Это связано с тем, что переключение больше, чем доступные биты, безусловно, переполняется. Если R был объявлен как short, результат int операции сдвига будет неявно преобразован в short; сужение преобразования, что может привести к реализации, определяемой реализацией, если значение не представляется в целевом типе.

Левый сдвиг

Результат E1 < E2 - левые сдвинутые позиции E2; освобожденные биты заполняются нулями. Если E1 имеет неподписанный тип, то результатом будет E1 × 2 E2 уменьшенный по модулю на один больше максимального значения, представляемого в типе результата. Если E1 имеет подписанный тип и неотрицательное значение, а E1 × 2 E2 является представимым в типе результата, то это результирующее значение; в противном случае поведение undefined.

Поскольку левые сдвиги одинаковы для обоих, освобожденные биты просто заполняются нулями. Затем он утверждает, что для обоих беззнаковых и подписанных типов это арифметический сдвиг. Я интерпретирую его как арифметический сдвиг, так как логические сдвиги не беспокоятся о значении, представленном битами, он просто рассматривает его как поток бит; но стандарт говорит не о битах, а определяет его в терминах значения, полученного произведением E1 с 2 E2.

Предостережение здесь заключается в том, что для подписанных типов значение должно быть неотрицательным, а результирующее значение должно быть представлено в типе результата. В противном случае операция undefined. Тип результата будет типом E1 после применения целостной рекламы, а не назначения (тип переменной, который будет удерживать результат). Полученное значение неявно преобразуется в тип назначения; если он не представлен в этом типе, то преобразование определяется реализацией (C99 §6.3.1.3/3).

Если E1 является подписанным типом с отрицательным значением, то поведение сдвига слева undefined. Это простой способ поведения undefined, который можно легко упустить.

Правый сдвиг

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

Сдвиг вправо для неподписанных и подписанных неотрицательных значений довольно прост; свободные разряды заполняются нулями. Для подписанных отрицательных значений результат правого сдвига определяется реализацией. При этом большинство реализаций, таких как GCC и Visual С++ реализовать правое смещение как арифметическое смещение, сохраняя знаковый бит.

Заключение

В отличие от Java, у которого есть специальный оператор >>> для логического переключения, кроме обычных >> и <<, C и С++ имеют только арифметическое смещение с некоторыми областями, оставшимися undefined и определенными реализацией. Причина, по которой я считаю их арифметикой, обусловлена ​​стандартной формулировкой операции математически, а не обработкой сдвинутого операнда в виде потока бит; это, возможно, причина, по которой она оставляет эти области un/implementation-defined вместо того, чтобы просто определять все случаи как логические сдвиги.

Ответ 4

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

~0 >> 1

К сожалению, это вызовет у вас проблемы, потому что в маске будут установлены все свои биты, потому что сдвинутое значение (~ 0) подписывается, таким образом выполняется арифметический сдвиг. Вместо этого вы хотите принудительно выполнить логический сдвиг, явно объявив значение как unsigned, т.е. Выполнив что-то вроде этого:

~0U >> 1;

Ответ 5

Вот функции, гарантирующие логический сдвиг вправо и арифметический сдвиг вправо int в C:

int logicalRightShift(int x, int n) {
    return (unsigned)x >> n;
}
int arithmeticRightShift(int x, int n) {
    if (x < 0 && n > 0)
        return x >> n | ~(~0U >> n);
    else
        return x >> n;
}

Ответ 6

Когда вы это сделаете  - сдвиг влево на 1 умножается на 2  - сдвиг вправо на 1 вы делите на 2

 x = 5
 x >> 1
 x = 2 ( x=5/2)

 x = 5
 x << 1
 x = 10 (x=5*2)

Ответ 7

Хорошо, я посмотрел на wikipedia, и у них есть это, чтобы сказать:

C, однако, имеет только один сдвиг вправо оператор, → . Многие компиляторы C выбирают которые сдвигаются вправо в зависимости от по типу целого числа сдвинуты; часто встречающиеся целые числа сдвинутый с использованием арифметического сдвига, и целые числа без знака сдвинуты используя логический сдвиг.

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

Ответ 8

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

будет использовать это, когда как это делают другие компиляторы.

Ответ 9

Левый сдвиг <<

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

Но в правом сдвиге >> мы должны следовать еще одному правилу и это правило называется "копией бита знака". Значение "знакового бита" означает, что если старший бит (MSB) установлен, то после правого сдвига снова MSB будет установлен, если он был reset, тогда он снова будет reset, означает, предыдущее значение было нулевым, а после смещения снова бит равен нулю, если предыдущий бит был равен, а затем после сдвига снова один. Это правило не применяется для сдвига влево.

Самый важный пример сдвига вправо, если вы смещаете любое отрицательное число на правый сдвиг, затем после некоторого сдвига значение, наконец, достигает нуля, а затем после этого, если сдвиг это -1, любое число раз останется таким же. Пожалуйста, проверьте.

Ответ 10

GCC делает

  • для -ve → Арифметический сдвиг

  • Для + ve → Логический сдвиг

Ответ 11

В соответствии со многими c составители:

  • << - это арифметический сдвиг влево или побитовый сдвиг влево.
  • >> - арифметический сдвиг поправочного правого сдвига вправо.