Арифметический сдвиг
Сдвиг вправо на один разряд делит число на два — но только если правильно обойтись со знаком. Логический сдвиг вдвигает слева ноль и портит отрицательные числа, а арифметический повторяет знаковый бит, и −4 превращается в −2.
Обновлено Редакция GAW
Арифметический сдвиг (arithmetic shift) — сдвиг двоичного числа со знаком, при котором знак сохраняется. При сдвиге вправо в старший разряд вдвигается копия знакового бита, а не ноль, поэтому для чисел в дополнительном коде сдвиг на n разрядов вправо — деление на 2n с округлением вниз. Сдвиг влево у арифметического и логического вариантов одинаков: справа вдвигаются нули.
Пример в 8 битах
| Число | Код | LSR (логический) | ASR (арифметический) |
|---|---|---|---|
| 16 | 0x10 | 0x08 = 8 | 0x08 = 8 |
| −4 | 0xFC | 0x7E = 126 | 0xFE = −2 |
| −5 | 0xFB | 0x7D = 125 | 0xFD = −3 |
Пример с 0x10 и 0xFC — из руководства AVR (команда ASR), строка −5 — наш расчёт. Видно, что ASR округляет к минус бесконечности: −5 / 2 = −2,5 превращается в −3, тогда как деление / в C округляет к нулю и даёт −2.
Команды процессоров
- AVR: ASR сдвигает регистр вправо, бит 7 сохраняется, бит 0 уходит во флаг переноса — Microchip отмечает, что по нему можно округлить результат. LSR — логический сдвиг, в бит 7 вдвигается 0.
- Cortex-M: ASR и LSR с непосредственным сдвигом на 1–32 разряда; в форме с суффиксом S (ASRS, LSRS) флаг C получает последний выдвинутый бит, флаг V не меняется.
В языке C
По стандарту C11 (6.5.7) сдвиг вправо отрицательного знакового числа определяется реализацией, а сдвиг на отрицательное число или на ширину типа и больше — неопределённое поведение. GCC, по своей документации (Integers implementation), выполняет такой сдвиг с расширением знака, то есть как ASR, — см. статью о битовых операциях. Но для переносимого кода сдвигают беззнаковые значения, а знаковое деление пишут явно.
Не путать арифметический сдвиг с циклическим (ROR/ROL), при котором выдвинутый бит возвращается с другой стороны. Сдвиги и флаги AVR и Cortex-M показывает калькулятор двоичной арифметики, маски и сдвиги в C — в статье «Битовые операции в C».
Частые вопросы
Чем арифметический сдвиг отличается от логического?
Логический сдвиг вправо (LSR) вдвигает в старший бит ноль, арифметический (ASR) повторяет старый знаковый бит. Для положительных чисел результат одинаков, а для отрицательных логический сдвиг даёт большое положительное число: 0xFC (−4) после LSR — 0x7E = 126, после ASR — 0xFE = −2.
Делит ли арифметический сдвиг на 2 точно?
Для чётных чисел — да. Для нечётных отрицательных результат округляется к минус бесконечности: ASR(−5) даёт −3 (в GCC так же работает −5 >> 1, см. ниже), а деление −5 / 2 в C даёт −2, потому что оно округляет к нулю (наш расчёт по определениям). Выдвинутый бит у AVR попадает во флаг C, и по нему можно скорректировать округление.
Какой сдвиг делает >> в C для отрицательных чисел?
Стандарт C11 (6.5.7) оставляет это на усмотрение реализации: результат сдвига вправо отрицательного знакового числа определяется компилятором. Чтобы не зависеть от этого, сдвигают беззнаковые значения или делят явно.
Источники
- Microchip. AVR Instruction Set Manual, DS40002198B, 2021 — 6.6 ASR: все биты сдвигаются вправо, бит 7 сохраняется, бит 0 уходит во флаг C; фактически деление числа со знаком на 2 без изменения знака, по C можно округлить результат; V = N ⊕ C; пример 0x10 → 8, 0xFC (−4) → −2; 6.74 LSR: в бит 7 вдвигается 0, N = 0
- Arm. Armv7-M Architecture Reference Manual, ARM DDI 0403E.e — A2.2: псевдокод ASR_C, LSR_C — флаг C получает последний выдвинутый бит; A7.7.10 ASR (immediate): сдвиг на 1–32, флаг V не меняется
- GCC Manual: Integers implementation — сдвиг вправо отрицательного знакового числа GCC выполняет с расширением знака
- ISO/IEC 9899:201x (C11), Committee Draft N1570, 2011 — 6.5.7: целочисленные продвижения операндов; сдвиг на отрицательное число или не меньше ширины — неопределённое поведение; E1 >> E2 для отрицательного E1 со знаком — определяется реализацией
Нашли ошибку?
Смотрите также
- Двоичная арифметика и флагиСложение, вычитание, умножение, деление, сдвиги; флаги C, V, N, Z AVR и Cortex-M
- Дополнительный кодTwo's complement: запись отрицательных целых
- Битовые операции в CМаски, сдвиги, установка и проверка битов, ловушки стандарта C
- Флаг переноса (C)Перенос из старшего разряда и заём при вычитании