Операторы сдвига

b

Миф №1: Сдвиг всегда быстрее умножения — это магическое ускорение

Многие разработчики на Delphi свято верят, что замена Value * 2 на Value shl 1 автоматически сделает код в 10 раз быстрее. На практике на современных компиляторах (Embarcadero Delphi 10.x, 11, 2026 года) разница в скорости между этими операциями для целых чисел составляет менее 1-3% на уровне машинных инструкций.

Современные процессоры имеют аппаратные умножители, которые выполняют умножение на степень двойки за один такт, как и сдвиг. Истинное преимущество сдвига проявляется не в скорости, а в читаемости кода, когда вы работаете с битовыми масками или флагами. Например, Flags := Flags or (1 shl 5) гораздо понятнее, чем Flags := Flags or 32, если вы работаете с битовыми полями.

Вывод: Не используйте сдвиг как «святой грааль» ускорения. Используйте его там, где он делает код самодокументированным — при работе с битами, а не для экономии одного такта.

Миф №2: Операторы ">>" и "<<" в Delphi работают точно как в C++

Это опасное заблуждение, которое приводит к трудноуловимым багам. В Delphi есть только два оператора сдвига: shl (сдвиг влево) и shr (сдвиг вправо). Они не зависят от знака числа: shr всегда выполняет логический (беззнаковый) сдвиг, заполняя освободившиеся биты нулями.

В отличие от C++, где >> для знаковых чисел может делать арифметический сдвиг (с сохранением знакового бита), в Delphi shr никогда не сохраняет знак. Пример: -8 shr 1 в Delphi даст 2147483644 (из-за логического сдвига), а не -4. Если вам нужен сдвиг с учётом знака, используйте деление: Value div 2 — это правильно и читаемо.

Совет: Всегда предполагайте, что shr — это только беззнаковая операция. Для знаковых чисел явно используйте деление.

Миф №3: Сдвиг безопасен для любых типов данных — Integer, Byte и т.д.

Распространенная ошибка — сдвигать числа за пределы разрядности типа. Например, попытка выполнить B shl 8, где B: Byte (8 бит), приведет к переполнению. Компилятор Delphi не выдаст предупреждение, но результат будет неверным — сдвиг произойдет, но старшие биты будут потеряны.

Правильный подход: приводите операнды к более широкому типу перед сдвигом. Если вам нужно сдвинуть 8-битное значение на 8 позиций, сделайте так:

Практическое правило: Если результат сдвига может превысить диапазон исходного типа, сначала приведите к Integer или Cardinal.

Миф №4: Операции сдвига с отрицательным числом битов запрещены

Некоторые разработчики боятся использовать сдвиг с переменной, полагая, что если значение счетчика отрицательное, код «упадет» с ошибкой. На самом деле в Delphi (и в большинстве других языков) сдвиг на отрицательное количество бит — это неопределенное поведение. Ваша программа не вызовет исключение, а просто выполнит сдвиг на 32 - (abs(Count) mod 32) или на Count and $1F — это зависит от оптимизаций компилятора.

Это приводит к абсолютно непредсказуемым результатам, которые сложно отлаживать. Пример из практики: код Value shl (SomeVar - 10) при SomeVar = 5 даст сдвиг на -5, что эквивалентно сдвигу на 27 (в 32-битной системе), а не на -5.

  1. Всегда проверяйте диапазон счетчика сдвига: if (Count >= 0) and (Count < 32).
  2. Для отрицательных счетчиков используйте сдвиг в другую сторону: if Count < 0 then Value shr (-Count) else Value shl Count.
  3. Избегайте вычислений прямо в операторе сдвига; выносите счетчик в отдельную переменную.
  4. В Delphi 2026 и старше используйте функции из модуля System.Math для безопасных битовых операций.

Миф №5: Сдвиг вправо (shr) эквивалентен делению на степень двойки — всегда

Это верно только для беззнаковых типов. Для знаковых целых (Integer, SmallInt и т.д.) Value shr N не эквивалентен Value div (2^N) из-за особенностей округления отрицательных чисел.

Пример: -5 shr 1 даст 2147483645, что явно не равно -5 div 2 = -3 (в Delphi деление округляется к нулю). Кроме того, логический сдвиг не сохраняет знак, поэтому для отрицательных чисел результат будет положительным и огромным.

Заключение: как перестать бояться и начать использовать сдвиги правильно

Операторы сдвига — мощный инструмент, но только если понимать их реальную механику. Главные правила для практического применения:

  1. Используйте shl и shr только для работы с битовыми масками, флагами и целыми беззнаковыми числами.
  2. Проверяйте величину сдвига: она должна быть от 0 до (разрядность типа - 1).
  3. Для знаковых чисел забудьте про сдвиг как арифметическую операцию — применяйте div и *.
  4. Если сомневаетесь в производительности — профилируйте. Разница между сдвигом и умножением/делением на степень двойки в Delphi 2026 составляет единицы тактов.
  5. Пишите код, который легко читается. Flags := Flags and not (1 shl 3) лучше, чем Flags := Flags and $FFFFFFF7.

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

Добавлено: 27.04.2026