Что делают побитовые операции
Побитовая операция рассматривает число как ряд битов и обрабатывает каждую позицию независимо. Это самые быстрые операции, какие есть у CPU, и встречаются они повсюду: флаги прав доступа, сетевые маски, хеш-функции, графика, сжатие и разбор протоколов.
| Операция | Символ (C, JS, Go, Python) | Правило для бита | Типичное применение |
|---|---|---|---|
| AND | & | 1, только если оба бита равны 1 | Маскировать биты, проверить флаг: x & FLAG |
| OR | | | 1, если хотя бы один бит равен 1 | Установить флаг: x | FLAG |
| XOR | ^ | 1, если биты различаются | Переключить флаг, обмен значений, контрольные суммы |
| NOT | ~ | Инвертирует каждый бит | Построить обратную маску: x & ~FLAG сбрасывает флаг |
| Сдвиг влево | << | Сдвигает биты влево, заполняет нулями | Умножить на 2ⁿ, построить маску: 1 << n |
| Сдвиг вправо | >> | Сдвигает биты вправо | Разделить на 2ⁿ, извлечь поле |
| Циклический сдвиг | rotl / rotr | Сдвиг, при котором выпавшие биты возвращаются с другого конца | Хеши и шифры |
Арифметический и логический сдвиг вправо
Сдвиг вправо бывает двух видов. Логический сдвиг заполняет новые старшие биты нулями. Арифметический копирует знаковый бит, поэтому отрицательное число в дополнительном коде остаётся отрицательным. В C и Go >> для знакового типа — арифметический, для беззнакового — логический. В JavaScript есть оба: >> — арифметический, >>> — логический. Установите здесь разрядность 8 и попробуйте -8 >> 1, чтобы увидеть разницу.
Почему важна разрядность
В реальном регистре биты, сдвинутые за старший разряд, теряются, а NOT инвертирует ровно столько битов, сколько вмещает регистр. ~0 — это 255 в 8 битах, 65535 в 16 и 4294967295 в 32. Этот калькулятор всегда работает в выбранной разрядности, поэтому результаты совпадают с тем, что ваш код увидит в uint8_t, uint32 и так далее.
Распространённые идиомы
Вопросы
В чём разница между логическими и побитовыми операторами?
Логические операторы (&&, ||, !) рассматривают значения целиком как истину или ложь и дают один результат истина/ложь. Побитовые операторы (&, |, ^, ~) работают с каждым битом операндов и дают число. 6 && 3 — истина, а 6 & 3 равно 2.
Почему NOT даёт отрицательное число в моём коде?
Если переменная знаковая, старший бит — знаковый. ~5 в 32-битном дополнительном коде — это 0xFFFFFFFA, что знаковый int показывает как −6. Сохраните результат в беззнаковом типе или наложите маску (~5 & 0xFF = 250), чтобы увидеть сырой битовый шаблон.
Что происходит с битами, сдвинутыми за край?
В регистре фиксированной разрядности они отбрасываются. Поэтому сдвиг влево может потерять старшие биты (переполнение), а сдвиг вправо теряет младшие (именно поэтому x >> 1 округляет вниз). Циклические сдвиги отличаются: биты возвращаются с другого конца.
XOR числа с самим собой всегда даёт ноль?
Да. Каждый бит сравнивается с таким же битом, а XOR равных битов равен 0. Поэтому x ^ x = 0, и поэтому XOR позволяет найти единственный непарный элемент в списке, где всё остальное встречается дважды.
Можно ли использовать это для 64-битных значений?
Да. Инструмент считает с целыми числами произвольной точности и маскирует до выбранной разрядности, поэтому 64-битные операции точны, в отличие от встроенных операторов JavaScript, которые ограничены 32 битами.