# Побитовые операторы Побитовые операторы интерпретируют операнды как последовательность из 32 битов (нулей и единиц). Они производят операции, используя двоичное представление числа, и возвращают новую последовательность из 32 бит (число) в качестве результата. **Эта глава сложная, требует дополнительных знаний в программировании и не очень важная, вы можете пропустить её.** [cut] ## Формат 32-битного целого числа со знаком [#signed-format] Побитовые операторы в JavaScript работают с 32-битными целыми числами в их двоичном представлении. Это представление называется "32-битное целое со знаком, старшим битом слева и дополнением до двойки". Разберём, как устроены числа внутри подробнее, это необходимо знать для битовых операций с ними. ## Список операторов В следующей таблице перечислены все побитовые операторы. Далее операторы разобраны более подробно.
Оператор Использование Описание
Побитовое И (AND) a & b Ставит 1 на бит результата, для которого соответствующие биты операндов равны 1.
Побитовое ИЛИ (OR) a | b Ставит 1 на бит результата, для которого хотя бы один из соответствующих битов операндов равен 1.
Побитовое исключающее ИЛИ (XOR) a ^ b Ставит 1 на бит результата, для которого только один из соответствующих битов операндов равен 1 (но не оба).
Побитовое НЕ (NOT) ~a Заменяет каждый бит операнда на противоположный.
Левый сдвиг `a << b` Сдвигает двоичное представление a на b битов влево, добавляя справа нули.
Правый сдвиг, переносящий знак `a >> b` Сдвигает двоичное представление a на b битов вправо, отбрасывая сдвигаемые биты.
Правый сдвиг с заполнением нулями `a >>> b` Сдвигает двоичное представление a на b битов вправо, отбрасывая сдвигаемые биты и добавляя нули слева.
## Описание работы операторов Побитовые операторы работают следующим образом:
  1. Операнды преобразуются в 32-битные целые числа, представленные последовательностью битов. Дробная часть, если она есть, отбрасывается.
  2. Для бинарных операторов -- каждый бит в первом операнде рассматривается вместе с соответствующим битом второго операнда: первый бит с первым, второй со вторым и т.п. Оператор применяется к каждой паре бит, давая соответствующий бит результата.
  3. Получившаяся в результате последовательность бит интерпретируется как обычное число.
Посмотрим, как работают операторы, на примерах. ### & (Побитовое И) Выполняет операцию И над каждой парой бит. Результат `a & b` равен единице только когда оба бита `a` и `b` равны единице. Таблица истинности для `&`:
`a``b``a & b`
`0``0``0`
`0``1``0`
`1``0``0`
`1``1``1`
Пример: ```js 9 (по осн. 10) = 00000000000000000000000000001001 (по осн. 2) 14 (по осн. 10) = 00000000000000000000000000001110 (по осн. 2) -------------------------------- 14 & 9 (по осн. 10) = 00000000000000000000000000001000 (по осн. 2) = 8 (по осн. 10) ``` ### | (Побитовое ИЛИ) Выполняет операцию ИЛИ над каждой парой бит. Результат `a | b` равен 1, если хотя бы один бит из a,b равен 1. Таблица истинности для `|`:
`a``b``a | b`
`0``0``0`
`0``1``1`
`1``0``1`
`1``1``1`
Пример: ```js 9 (по осн. 10) = 00000000000000000000000000001001 (по осн. 2) 14 (по осн. 10) = 00000000000000000000000000001110 (по осн. 2) -------------------------------- 14 | 9 (по осн. 10) = 00000000000000000000000000001111 (по осн. 2) = 15 (по осн. 10) ``` ### ^ (Исключающее ИЛИ) Выполняет операцию "Исключающее ИЛИ" над каждой парой бит. a Исключающее ИЛИ b равно 1, если только a=1 или только b=1, но не оба одновременно a=b=1. Таблица истинности для исключающего ИЛИ:
`a``b``a ^ b`
`0``0``0`
`0``1``1`
`1``0``1`
`1``1``0`
Как видно, оно даёт 1, если ЛИБО слева `1`, ЛИБО справа `1`, но не одновременно. Поэтому его и называют "исключающее ИЛИ". Пример: ```js 9 (по осн. 10) = 00000000000000000000000000001001 (по осн. 2) 14 (по осн. 10) = 00000000000000000000000000001110 (по осн. 2) -------------------------------- 14 ^ 9 (по осн. 10) = 00000000000000000000000000000111 (по осн. 2) = 7 (по осн. 10) ``` [smart header="Исключающее ИЛИ в шифровании"] Исключающее или можно использовать для шифрования, так как эта операция полностью обратима. Двойное применение исключающего ИЛИ с тем же аргументом даёт исходное число. Иначе говоря, верна формула: `a ^ b ^ b == a`. Пускай Вася хочет передать Пете секретную информацию `data`. Эта информация заранее превращена в число, например строка интерпретируется как последовательность кодов символов. Вася и Петя заранее договариваются о числовом ключе шифрования `key`. Алгоритм: Например, пусть в `data` очередное число равно `9`, а ключ `key` равен `1220461917`. ```js Данные: 9 в двоичном виде 00000000000000000000000000001001 Ключ: 1220461917 в двоичном виде 01001000101111101100010101011101 Результат операции 9 ^ key: 01001000101111101100010101010100 Результат в 10-ной системе (шифровка): 1220461908 ``` В нашем случае: ```js Полученная шифровка в двоичной системе: 9 ^ key = 1220461908 01001000101111101100010101010100 Ключ: 1220461917 в двоичном виде: 01001000101111101100010101011101 Результат операции 1220461917 ^ key: 00000000000000000000000000001001 Результат в 10-ной системе (исходное сообщение): 9 ``` Конечно, такое шифрование поддаётся частотному анализу и другим методам дешифровки, поэтому современные алгоритмы используют операцию XOR `^` как одну из важных частей более сложной многоступенчатой схемы. [/smart] ### ~ (Побитовое НЕ) Производит операцию НЕ над каждым битом, заменяя его на обратный ему. Таблица истинности для НЕ:
`a``~a`
`0``1`
`1``0`
Пример: ```js 9 (по осн. 10) = 00000000000000000000000000001001 (по осн. 2) -------------------------------- ~9 (по осн. 10) = 11111111111111111111111111110110 (по осн. 2) = -10 (по осн. 10) ``` Из-за внутреннего представления отрицательных чисел получается так, что `~n == -(n+1)`. Например: ```js //+ run alert(~3); // -4 alert(~-1); // 0 ``` ### << (Битовый сдвиг влево) Операторы битового сдвига принимают два операнда. Первый -- это число для сдвига, а второй -- количество битов, которые нужно сдвинуть в первом операнде. Оператор `<<` сдвигает первый операнд на указанное число битов влево. Лишние биты отбрасываются, справа добавляются нулевые биты. Например, `9 << 2` даст `36`: ```js 9 (по осн.10) = 00000000000000000000000000001001 (по осн.2) -------------------------------- 9 << 2 (по осн.10) = 00000000000000000000000000100100 (по осн.2) = 36 (по осн.10) ``` Операция `<< 2` сдвинула и отбросила два левых нулевых бита и добавила справа два новых нулевых. [smart header="Левый сдвиг почти равен умножению на 2"] Битовый сдвиг `<< N` обычно имеет тот же эффект, что и умножение на два `N` раз, например: ```js //+ run alert( 3 << 1 ); // 6, умножение на 2 alert( 3 << 2 ); // 12, умножение на 2 два раза alert( 3 << 3 ); // 24, умножение на 2 три раза ``` Конечно, следует иметь в виду, что побитовые операторы работают только с 32-битными числами, поэтому верхний порог такого "умножения" ограничен: ```js //+ run *!* alert(10000000000 << 1); // -1474836480, отброшен крайний-левый бит */!* alert(10000000000 * 2); // 20000000000, обычное умножение ``` [/smart] ### >> (Правый битовый сдвиг, переносящий знак) Этот оператор сдвигает биты вправо, отбрасывая лишние. При этом слева добавляется *копия* крайнего-левого бита. Знак числа (представленный крайним-левым битом) при этом не меняется, так как новый крайний-левый бит имеет то же значение, что и исходном числе. Поэтому он назван "переносящим знак". Например, `9 >> 2` даст 2: ```js 9 (по осн.10) = 00000000000000000000000000001001 (по осн.2) -------------------------------- 9 >> 2 (по осн.10) = 00000000000000000000000000000010 (по осн.2) = 2 (по осн.10) ``` Операция `>> 2` сдвинула вправо и отбросила два правых бита `01` и добавила слева две копии первого бита `00`. Аналогично, `-9 >> 2` даст `-3`: ```js -9 (по осн.10) = 11111111111111111111111111110111 (по осн.2) -------------------------------- -9 >> 2 (по осн.10) = 11111111111111111111111111111101 (по осн.2) = -3 (по осн.10) ``` Здесь операция `>> 2` сдвинула вправо и отбросила два правых бита `11` и добавила слева две копии первого бита `11`. , Знак числа сохранён, так как крайний-левый (знаковый) бит сохранил значение `1`. [smart header="Правый сдвиг почти равен целочисленному делению на 2"] Битовый сдвиг `>> N` обычно имеет тот же, что и целочисленное деление на два `N` раз: ```js //+ run alert( 100 >> 1 ); // 50, деление на 2 alert( 100 >> 2 ); // 25, деление на 2 два раза alert( 100 >> 3 ); // 12, деление на 2 три раза, целая часть от результата ``` [/smart] ### >>> (Правый сдвиг с заполнением нулями) Этот оператор сдвигает биты первого операнда вправо. Лишние биты справа отбрасываются. Слева добавляются нулевые биты. Знаковый бит становится равным 0, поэтому результат всегда положителен. **Для неотрицательных чисел правый сдвиг с заполнением нулями `>>>` и правый сдвиг с переносом знака `>>` дадут одинаковый результат, т.к в обоих случаях слева добавятся нули.** Для отрицательных чисел -- результат работы разный. Например, `-9 >>> 2` даст `1073741821`, в отличие от `-9 >> 2` (дает `-3`): ```js -9 (по осн.10) = 11111111111111111111111111110111 (по осн.2) -------------------------------- -9 >>> 2 (по осн.10) = 00111111111111111111111111111101 (по осн.2) = 1073741821 (по осн.10) ``` ## Применение побитовых операторов Побитовые операторы используются редко, но всё же используются. Случаи применения побитовых операторов, которые мы здесь разберём, составляют большинство всех использований в JavaScript. [warn header="Осторожно, приоритеты!"] В JavaScript побитовые операторы `^`, `&`, `|` выполняются после сравнений `==`. Например, в сравнении `a == b^0` будет сначала выполнено сравнение `a == b`, а потом уже операция `^0`, как будто стоят скобки `(a == b)^0`. Обычно это не то, чего мы хотим. Чтобы гарантировать желаемый порядок, нужно ставить скобки: `a == (b^0)`. [/warn] ### Маска Для этого примера представим, что наш скрипт работает с пользователями: У каждого из них есть ряд доступов, которые можно свести в таблицу:
Пользователь Просмотр статей Изменение статей Просмотр товаров Изменение товаров Управление правами
Гость Да Нет Да Нет Нет
Редактор Да Да Да Да Нет
Админ Да Да Да Да Да
Если вместо "Да" поставить `1`, а вместо "Нет" -- `0`, то каждый набор доступов описывается числом:
Пользователь Просмотр статей Изменение статей Просмотр товаров Изменение товаров Управление правами В 10-ной системе
Гость 1 0 1 0 0 = 20
Редактор 1 1 1 1 0 = 30
Админ 1 1 1 1 1 = 31
**Мы "упаковали" много информации в одно число. Это экономит память. Но, кроме этого, по нему очень легко проверить, имеет ли посетитель заданную *комбинацию доступов*!** Для этого посмотрим, как в 2-ной системе представляется каждый доступ в отдельности. Например, просматривать и изменять статьи позволит доступ `access = 11000`. Как правило, доступы задаются в виде констант: ```js var ACCESS_ADMIN = 1; // 00001 var ACCESS_GOODS_CHANGE = 2; // 00010 var ACCESS_GOODS_VIEW = 4; // 00100 var ACCESS_ARTICLE_CHANGE = 8; // 01000 var ACCESS_ARTICLE_VIEW = 16; // 10000 ``` Из этих констант получить нужную комбинацию доступов можно при помощи операции `|`. ```js var access = ACCESS_ARTICLE_VIEW | ACCESS_ARTICLE_CHANGE; // 11000 ``` ### Двоичные числа в JavaScript Для удобной работы с примерами в этой статье пригодятся две функции. Например: ```js //+ run var access = parseInt("11000", 2); // получаем число из строки alert(access); // 24, число с таким 2-ным представлением var access2 = access.toString(2); // обратно двоичную строку из числа alert(access2); // 11000 ``` ### Проверка доступов **Для того, чтобы понять, есть ли в доступе `access` нужный доступ, например управление правами -- достаточно применить к нему побитовый оператор И (`&`) с соответствующей маской.** Создадим для примера ряд доступов и проверим их: ```js //+ run var access = parseInt("11111", 2); // 31, все 1 означает, что доступ полный alert(access & ACCESS_ADMIN); // если результат не 0, то есть доступ ACCESS_ADMIN ``` А теперь та же проверка для посетителя с другими правами: ```js //+ run var access = parseInt("10100"); // 20, нет 1 в конце alert(access & ACCESS_ADMIN); // 0, нет доступа к управлению правами ``` Такая проверка работает, потому что оператор И ставит `1` на те позиции результата, на которых в обоих операндах стоит `1`. Так что `access & 1` для любого числа `access` поставит все биты в ноль, кроме самого правого. А самый правый станет `1` только если он равен `1` в `access`. Для полноты картины также проверим, даёт ли доступ `11111` право на изменение товаров. Для этого нужно применить к доступу оператор И (`&`) с `00010` (=`2` в 10-ной системе). ```js //+ run var adminAccess = 31; // 111*!*1*/!*1 alert(adminAccess & ACCESS_GOODS_CHANGE); // не 0, есть доступ к изменению товаров ``` **Можно проверить один из нескольких доступов.** Например, проверим, есть ли права на просмотр ИЛИ изменение товаров. Соответствующие права задаются битом `1` на втором и третьем месте с конца, что даёт число `00110` (=`6` в 10-ной системе). ```js //+ run var check = ACCESS_GOODS_VIEW | ACCESS_GOODS_CHANGE; // 6, 00110 var access = 30; // 11*!*11*/!*0; alert(access & check); // не 0, значит есть доступ к просмотру ИЛИ изменению access = parseInt("11100", 2); alert(access & check); // не 0, есть доступ к просмотру ИЛИ изменению ``` Как видно из примера выше, если в аргументе `check` стоит ИЛИ из нескольких доступов `ACCESS_*`, то и результат проверки скажет, есть ли хотя бы один из них. А какой -- нужно смотреть отдельной проверкой, если это важно. **Итак, маска даёт возможность удобно "паковать" много битовых значений в одно число при помощи ИЛИ `|`, а также, при помощи оператора И (`&`), проверять маску на комбинацию установленных битов.** ### Маски в функциях Зачастую маски используют в функциях, чтобы одним параметром передать несколько "флагов", т.е. однобитных значений. Например: ```js // найти пользователей с правами на изменение товаров или администраторов findUsers(ACCESS_GOODS_CHANGE | ACCESS_ADMIN); ``` ### Округление **Так как битовые операции отбрасывают десятичную часть, то их можно использовать для округления. Достаточно взять любую операцию, которая не меняет значение числа.** Например, двойное НЕ (`~`): ```js //+ run alert( ~~12.345 ); // 12 ``` Подойдёт и Исключающее ИЛИ (`^`) с нулём: ```js //+ run alert( 12.345^0 ); // 12 ``` Последнее даже более удобно, поскольку отлично читается: ```js //+ run alert( 12.3 * 14.5 ^ 0); // (=178) "12.3 умножить на 14.5 *!*и округлить*/!*" ``` У побитовых операторов достаточно низкий приоритет, он меньше чем у остальной арифметики: ```js //+ run alert( 1.1 + 1.2 ^ 0 ); // 2, сложение выполнится раньше округления ``` ### Проверка на -1 [Внутренний формат](#signed-format) чисел устроен так, что для смены знака нужно все биты заменить на противоположные ("обратить") и прибавить `1`. Обращение битов -- это побитовое НЕ (`~`). То есть, при таком формате представления числа `-n = ~n + 1`. Или, если перенести единицу: `~n = -(n+1)`. Как видно из последнего равенства, `~n == 0` только если `n == -1`. Поэтому можно легко проверить равенство `n == -1`: ```js //+ run var n = 5; if (~n) { // сработает, т.к. ~n = -(5+1) = -6 alert("n не -1"); // выведет! } ``` ```js //+ run var n = -1; if (~n) { // не сработает, т.к. ~n = -(-1+1) = 0 alert("...ничего не выведет..."); } ``` Проверка на `-1` пригождается, например, при поиске символа в строке. Вызов `str.indexOf("подстрока")` возвращает позицию подстроки в `str`, или `-1` если не нашёл. ```js //+ run var str = "Проверка"; if (~str.indexOf("верка")) { // Сочетание "if (~...indexOf)" читается как "если найдено" alert('найдено!'); } ``` ### Умножение и деление на степени 2 **Оператор `a << b`, сдвигая биты, по сути умножает `a` на 2b.** Например: ```js //+ run alert( 1 << 2 ); // 1*(2*2) = 4 alert( 1 << 3 ); // 1*(2*2*2) = 8 alert( 3 << 3 ); // 3*(2*2*2) = 24 ``` При этом следует иметь в виду, что максимальный верхний порог такого умножения меньше, чем обычно, так как побитовый оператор оперирует 32-битными целыми, в то время как обычные операторы оперируют числами длиной 64 бита. **Оператор `a >> b`, сдвигая биты, производит целочисленное деление `a` на 2b.** ```js //+ run alert( 8 >> 2 ); // 2 = 8/4, убрали 2 нуля в двоичном представлении alert( 11 >> 2 ); // 2, целочисленное деление (менее значимые биты просто отброшены) ``` ## Итого Как правило, битовое представление числа используется для: [head] [/head]