752 lines
No EOL
34 KiB
Markdown
752 lines
No EOL
34 KiB
Markdown
# Побитовые операторы
|
||
|
||
Побитовые операторы интерпретируют операнды как последовательность из 32 битов (нулей и единиц). Они производят операции, используя двоичное представление числа, и возвращают новую последовательность из 32 бит (число) в качестве результата.
|
||
|
||
**Эта глава сложная, требует дополнительных знаний в программировании и не очень важная, вы можете пропустить её.**
|
||
[cut]
|
||
|
||
## Формат 32-битного целого числа со знаком [#signed-format]
|
||
|
||
Побитовые операторы в JavaScript работают с 32-битными целыми числами в их двоичном представлении.
|
||
|
||
Это представление называется "32-битное целое со знаком, старшим битом слева и дополнением до двойки".
|
||
|
||
Разберём, как устроены числа внутри подробнее, это необходимо знать для битовых операций с ними.
|
||
|
||
<ul>
|
||
<li>Что такое [двоичная система счисления](http://ru.wikipedia.org/wiki/%C4%E2%EE%E8%F7%ED%E0%FF_%F1%E8%F1%F2%E5%EC%E0_%F1%F7%E8%F1%EB%E5%ED%E8%FF), вам, надеюсь, уже известно. При разборе побитовых операций мы будем обсуждать именно двоичное представление чисел, из 32 бит.
|
||
|
||
</li>
|
||
<li>*Старший бит слева* -- это научное название для самого обычного порядка записи цифр (от большего разряда к меньшему). При этом, если больший разряд отсутствует, то соответствующий бит равен нулю.
|
||
|
||
Примеры представления чисел в двоичной системе:
|
||
|
||
```js
|
||
a = 0; // 00000000000000000000000000000000
|
||
a = 1; // 00000000000000000000000000000001
|
||
a = 2; // 00000000000000000000000000000010
|
||
a = 3; // 00000000000000000000000000000011
|
||
a = 255;// 00000000000000000000000011111111
|
||
```
|
||
|
||
Обратите внимание, каждое число состоит ровно из 32-битов.
|
||
|
||
[smart header="Младший бит слева"]
|
||
Несмотря на то, что нам такой способ записи чисел кажется не совсем обычным, бывают языки и технологии, использующие способ записи "младший бит слева", когда биты пишутся наоборот, от меньшего разряда к большему.
|
||
|
||
Именно поэтому спецификация EcmaScript явно говорит "старший бит слева".
|
||
[/smart]
|
||
|
||
</li>
|
||
<li>*Дополнение до двойки* -- это название способа поддержки отрицательных чисел.
|
||
|
||
**Двоичный вид числа, обратного данному (например, `5` и `-5`) получается путём обращения всех битов с прибавлением 1.**
|
||
|
||
То есть, нули заменяются на единицы, единицы -- на нули и к числу прибавляется `1`. Получается внутреннее представление того же числа, но со знаком минус.
|
||
|
||
Например, вот число `314`:
|
||
|
||
```js
|
||
00000000000000000000000100111010
|
||
```
|
||
|
||
Чтобы получить `-314`, первый шаг -- обратить биты числа: заменить `0` на `1`, а `1` на `0`:
|
||
|
||
```js
|
||
11111111111111111111111011000101
|
||
```
|
||
|
||
Второй шаг -- к полученному двоичному числу приплюсовать единицу, обычным двоичным сложением: `11111111111111111111111011000101 + 1 = 11111111111111111111111011000110`.
|
||
|
||
Итак, мы получили:
|
||
|
||
```js
|
||
-314 = 11111111111111111111111011000110
|
||
```
|
||
|
||
Принцип дополнения до двойки делит все двоичные представления на два множества: если крайний-левый бит равен `0` -- число положительное, если `1` -- число отрицательное. Поэтому этот бит называется <i>знаковым битом</i>.
|
||
</li>
|
||
</ul>
|
||
|
||
## Список операторов
|
||
|
||
В следующей таблице перечислены все побитовые операторы.
|
||
Далее операторы разобраны более подробно.
|
||
|
||
<table class="fullwidth-table">
|
||
<tr>
|
||
<th>Оператор</th>
|
||
<th>Использование</th>
|
||
<th>Описание</th>
|
||
|
||
</tr>
|
||
<tr>
|
||
<td>Побитовое И (AND)</td>
|
||
<td style="white-space: nowrap"><code>a & b</code></td>
|
||
<td>Ставит 1 на бит результата, для которого соответствующие биты операндов равны 1.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Побитовое ИЛИ (OR)</td>
|
||
<td style="white-space: nowrap"><code>a | b</code></td>
|
||
<td>Ставит 1 на бит результата, для которого хотя бы один из соответствующих битов операндов равен 1.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Побитовое исключающее ИЛИ (XOR)</td>
|
||
<td style="white-space: nowrap"><code>a ^ b</code></td>
|
||
<td>Ставит 1 на бит результата, для которого только один из соответствующих битов операндов равен 1 (но не оба).</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Побитовое НЕ (NOT)</td>
|
||
<td style="white-space: nowrap"><code>~a</code></td>
|
||
<td>Заменяет каждый бит операнда на противоположный.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Левый сдвиг</td>
|
||
<td style="white-space: nowrap">`a << b`</td>
|
||
<td>Сдвигает двоичное представление <code>a</code> на <code>b</code> битов влево, добавляя справа нули.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Правый сдвиг, переносящий знак</td>
|
||
<td style="white-space: nowrap">`a >> b`</td>
|
||
<td>Сдвигает двоичное представление <code>a</code> на <code>b</code> битов вправо, отбрасывая сдвигаемые биты.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Правый сдвиг с заполнением нулями</td>
|
||
<td style="white-space: nowrap">`a >>> b`</td>
|
||
<td>Сдвигает двоичное представление <code>a</code> на <code>b</code> битов вправо, отбрасывая сдвигаемые биты и добавляя нули слева.</td>
|
||
</tr>
|
||
</table>
|
||
|
||
## Описание работы операторов
|
||
|
||
Побитовые операторы работают следующим образом:
|
||
|
||
<ol>
|
||
<li>Операнды преобразуются в 32-битные целые числа, представленные последовательностью битов. Дробная часть, если она есть, отбрасывается.</li>
|
||
<li>Для бинарных операторов -- каждый бит в первом операнде рассматривается вместе с соответствующим битом второго операнда: первый бит с первым, второй со вторым и т.п. Оператор применяется к каждой паре бит, давая соответствующий бит результата.</li>
|
||
<li>Получившаяся в результате последовательность бит интерпретируется как обычное число.</li>
|
||
</ol>
|
||
|
||
Посмотрим, как работают операторы, на примерах.
|
||
|
||
### & (Побитовое И)
|
||
|
||
Выполняет операцию И над каждой парой бит.
|
||
|
||
Результат `a & b` равен единице только когда оба бита `a` и `b` равны единице.
|
||
|
||
Таблица истинности для `&`:
|
||
|
||
<table class="standard-table">
|
||
<tr><th>`a`</th><th>`b`</th><th>`a & b`</th></tr>
|
||
<tr><td>`0`</td><td>`0`</td><td>`0`</td></tr>
|
||
<tr><td>`0`</td><td>`1`</td><td>`0`</td></tr>
|
||
<tr><td>`1`</td><td>`0`</td><td>`0`</td></tr>
|
||
<tr><td>`1`</td><td>`1`</td><td>`1`</td></tr>
|
||
</table>
|
||
|
||
Пример:
|
||
|
||
```js
|
||
9 (по осн. 10)
|
||
= 00000000000000000000000000001001 (по осн. 2)
|
||
14 (по осн. 10)
|
||
= 00000000000000000000000000001110 (по осн. 2)
|
||
--------------------------------
|
||
14 & 9 (по осн. 10)
|
||
= 00000000000000000000000000001000 (по осн. 2)
|
||
= 8 (по осн. 10)
|
||
```
|
||
|
||
### | (Побитовое ИЛИ)
|
||
|
||
Выполняет операцию ИЛИ над каждой парой бит. Результат `a | b` равен 1, если хотя бы один бит из <code>a,b</code> равен 1.
|
||
|
||
Таблица истинности для `|`:
|
||
|
||
<table class="standard-table">
|
||
<tr><th>`a`</th><th>`b`</th><th>`a | b`</th></tr>
|
||
<tr><td>`0`</td><td>`0`</td><td>`0`</td></tr>
|
||
<tr><td>`0`</td><td>`1`</td><td>`1`</td></tr>
|
||
<tr><td>`1`</td><td>`0`</td><td>`1`</td></tr>
|
||
<tr><td>`1`</td><td>`1`</td><td>`1`</td></tr>
|
||
</table>
|
||
|
||
Пример:
|
||
|
||
```js
|
||
9 (по осн. 10)
|
||
= 00000000000000000000000000001001 (по осн. 2)
|
||
14 (по осн. 10)
|
||
= 00000000000000000000000000001110 (по осн. 2)
|
||
--------------------------------
|
||
14 | 9 (по осн. 10)
|
||
= 00000000000000000000000000001111 (по осн. 2)
|
||
= 15 (по осн. 10)
|
||
```
|
||
|
||
### ^ (Исключающее ИЛИ)
|
||
|
||
Выполняет операцию "Исключающее ИЛИ" над каждой парой бит.
|
||
|
||
<code>a</code> Исключающее ИЛИ <code>b</code> равно 1, если только <code>a=1</code> или только <code>b=1</code>, но не оба одновременно <code>a=b=1</code>.
|
||
|
||
Таблица истинности для исключающего ИЛИ:
|
||
|
||
<table class="standard-table">
|
||
<tr><th>`a`</th><th>`b`</th><th>`a ^ b`</th></tr>
|
||
<tr><td>`0`</td><td>`0`</td><td>`0`</td></tr>
|
||
<tr><td>`0`</td><td>`1`</td><td>`1`</td></tr>
|
||
<tr><td>`1`</td><td>`0`</td><td>`1`</td></tr>
|
||
<tr><td>`1`</td><td>`1`</td><td>`0`</td></tr>
|
||
</table>
|
||
|
||
Как видно, оно даёт 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`.
|
||
|
||
Алгоритм:
|
||
<ul>
|
||
<li>Вася берёт двоичное представление `data` и делает операцию `data ^ key`. При необходимости `data` бьётся на части, равные по длине `key`, чтобы можно было провести побитовое ИЛИ `^` для каждой части. В JavaScript оператор `^` работает с 32-битными целыми числами, так что `data` нужно разбить на последовательность таких чисел.</li>
|
||
<li>Результат `data ^ key` отправляется Пете, это шифровка.</li>
|
||
</ul>
|
||
|
||
Например, пусть в `data` очередное число равно `9`, а ключ `key` равен `1220461917`.
|
||
|
||
```js
|
||
Данные: 9 в двоичном виде
|
||
00000000000000000000000000001001
|
||
|
||
Ключ: 1220461917 в двоичном виде
|
||
01001000101111101100010101011101
|
||
|
||
Результат операции 9 ^ key:
|
||
01001000101111101100010101010100
|
||
Результат в 10-ной системе (шифровка):
|
||
1220461908
|
||
```
|
||
|
||
<ul>
|
||
<li>Петя, получив очередное число шифровки `1220461908`, применяет к нему такую же операцию `^ key`.</li>
|
||
<li>Результатом будет исходное число `data`.</li>
|
||
</ul>
|
||
|
||
В нашем случае:
|
||
|
||
```js
|
||
Полученная шифровка в двоичной системе:
|
||
9 ^ key = 1220461908
|
||
01001000101111101100010101010100
|
||
|
||
Ключ: 1220461917 в двоичном виде:
|
||
01001000101111101100010101011101
|
||
|
||
Результат операции 1220461917 ^ key:
|
||
00000000000000000000000000001001
|
||
Результат в 10-ной системе (исходное сообщение):
|
||
9
|
||
```
|
||
|
||
Конечно, такое шифрование поддаётся частотному анализу и другим методам дешифровки, поэтому современные алгоритмы используют операцию XOR `^` как одну из важных частей более сложной многоступенчатой схемы.
|
||
[/smart]
|
||
|
||
|
||
|
||
|
||
### ~ (Побитовое НЕ)
|
||
|
||
Производит операцию НЕ над каждым битом, заменяя его на обратный ему.
|
||
|
||
Таблица истинности для НЕ:
|
||
|
||
<table class="standard-table">
|
||
<tr><th>`a`</th><th>`~a`</th></tr>
|
||
<tr><td>`0`</td><td>`1`</td></tr>
|
||
<tr><td>`1`</td><td>`0`</td></tr>
|
||
</table>
|
||
|
||
Пример:
|
||
|
||
```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` даст <code>2</code>:
|
||
|
||
```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]
|
||
|
||
### Маска
|
||
|
||
Для этого примера представим, что наш скрипт работает с пользователями:
|
||
<ul>
|
||
<li>`Гость`</li>
|
||
<li>`Редактор`</li>
|
||
<li>`Админ`</li>
|
||
</ul>
|
||
|
||
У каждого из них есть ряд доступов, которые можно свести в таблицу:
|
||
|
||
<table>
|
||
<tr>
|
||
<th>Пользователь</th>
|
||
<th>Просмотр статей</th>
|
||
<th>Изменение статей</th>
|
||
<th>Просмотр товаров</th>
|
||
<th>Изменение товаров</th>
|
||
<th>Управление правами</th>
|
||
</tr>
|
||
<tr>
|
||
<td>Гость</td>
|
||
<td>Да</td>
|
||
<td>Нет</td>
|
||
<td>Да</td>
|
||
<td>Нет</td>
|
||
<td>Нет</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Редактор</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Нет</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Админ</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
<td>Да</td>
|
||
</tr>
|
||
</table>
|
||
|
||
Если вместо "Да" поставить `1`, а вместо "Нет" -- `0`, то каждый набор доступов описывается числом:
|
||
|
||
<table>
|
||
<tr>
|
||
<th>Пользователь</th>
|
||
<th>Просмотр статей</th>
|
||
<th>Изменение статей</th>
|
||
<th>Просмотр товаров</th>
|
||
<th>Изменение товаров</th>
|
||
<th>Управление правами</th>
|
||
<th>В 10-ной системе</th>
|
||
</tr>
|
||
<tr>
|
||
<td>Гость</td>
|
||
<td>1</td>
|
||
<td>0</td>
|
||
<td>1</td>
|
||
<td>0</td>
|
||
<td>0</td>
|
||
<td> = 20</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Редактор</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>0</td>
|
||
<td> = 30</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Админ</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td> = 31</td>
|
||
</tr>
|
||
</table>
|
||
|
||
**Мы "упаковали" много информации в одно число. Это экономит память. Но, кроме этого, по нему очень легко проверить, имеет ли посетитель заданную *комбинацию доступов*!**
|
||
|
||
Для этого посмотрим, как в 2-ной системе представляется каждый доступ в отдельности.
|
||
|
||
<ul>
|
||
<li>Доступ, соответствующий только управлению правами: `00001 (=1)` (все нули кроме `1` на позиции, соответствующей этому доступу).</li>
|
||
<li>Доступ, соответствующий только изменению товаров: `00010 (=2)`.</li>
|
||
<li>Доступ, соответствующий только просмотру товаров: `00100 (=4)`.</li>
|
||
<li>Доступ, соответствующий только изменению статей: `01000 (=8)`.</li>
|
||
<li>Доступ, соответствующий только просмотру статей: `10000 (=16)`.</li>
|
||
</ul>
|
||
|
||
Например, просматривать и изменять статьи позволит доступ `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
|
||
|
||
Для удобной работы с примерами в этой статье пригодятся две функции.
|
||
|
||
<ul>
|
||
<li>`parseInt("11000", 2)` -- переводит строку с двоичной записью числа в число.</li>
|
||
<li>`n.toString(2)` -- получает для числа `n` запись в 2-ной системе в виде строки.</li>
|
||
</ul>
|
||
|
||
Например:
|
||
|
||
```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` на <code>2<sup>b</sup></code>.**
|
||
|
||
Например:
|
||
|
||
```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` на <code>2<sup>b</sup></code>.**
|
||
|
||
```js
|
||
//+ run
|
||
alert( 8 >> 2 ); // 2 = 8/4, убрали 2 нуля в двоичном представлении
|
||
alert( 11 >> 2 ); // 2, целочисленное деление (менее значимые биты просто отброшены)
|
||
```
|
||
|
||
## Итого
|
||
|
||
<ul>
|
||
<li>Бинарные побитовые операторы: `& | ^ << >> >>>`.</li>
|
||
<li>Унарный побитовый оператор один: `~`.</li>
|
||
</ul>
|
||
|
||
Как правило, битовое представление числа используется для:
|
||
<ul>
|
||
<li>Упаковки нескольких битововых значений ("флагов") в одно значение. Это экономит память и позволяет проверять наличие комбинации флагов одним оператором `&`. Кроме того, такое упакованное значение будет для функции всего одним параметром, это тоже удобно.</li>
|
||
<li>Округления числа: `(12.34^0) = 12`.</li>
|
||
<li>Проверки на равенство `-1`: `if (~n) { n не -1 }`.</li>
|
||
</ul>
|
||
[head]
|
||
<script>
|
||
|
||
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
|
||
</script>
|
||
[/head] |