Битовые манипуляции в JavaScript
Вся информация на компьютерах хранится в двоичном формате. Буквы, цифры, массивы, хэшмапы, все.
Цифры в свою очередь эффективней хранить в двоичной системе счисления. Любое число в десятичной системе можно перевести в двоичную.
По сути все числа хранятся в таком формате:
Мы можем легко посмотреть репрезентацию десятичного числа в двоичной системе с помощью
toString
:10.tostring(2); // 1010
4.toString(2); // 100
2.toString(2); // 10
1.toString(2); // 1
Парсить данные числа в свою очередь удобно через
parseInt
:parseInt('1010', 2); // 10
parseInt('100', 2); // 4
parseInt('10', 2); // 2
parseInt('1', 2); // 1
Мы также можем записывать двоичные числа с помощью
0b
:console.log(0b1010); // 10
console.log(0b100); // 4
console.log(0b10); // 2
console.log(0b1); // 1
Логические Операторы
Для битовых чисел есть специальные логические операторы. Цель их существования - манипуляция с битовыми значениями.
«Битовый И»:
&
Оператор
&
сравнивает каждый разряд двоичного числа и отдает 1
только в том случае, если разряды в двух числах являются единицей:console.log(0b1001 & 0b0110) // 0000 => 0
console.log(0b1000 & 0b1100) // 1000 => 8
console.log(0b1111 & 0b1111) // 1111 => 1 + 2 + 4 + 8 => 15
«Битовый ИЛИ»:
|
Оператор
|
сравнивает каждый разряд двоичного числа, если один из разрядов равен 1
, то данный оператор вернет 1
:console.log(0b1001 | 0b0110) // 1111 => 15
console.log(0b1000 | 0b1100) // 1100 => 12
console.log(0b1111 | 0b1111) // 1111 => 1 + 2 + 4 + 8 => 15
«Битовый НЕ»:
~
Для того чтобы инверсировать все разряды в числе используется оператор
~
:console.group("0b0 (0)")
console.log(0b0); // 0
console.log(~0b0); // -1 (0b111111...111)
console.groupEnd();
console.group("0b1 (1)")
console.log(0b1); // 1
console.log(~0b1); // -2 (0b11111111...1110)
console.groupEnd();
console.group("0b100 (4)")
console.log(0b100); // 4
console.log(~0b100); // -5 (0b11111...11011)
console.groupEnd();
Вот как происходит изменение битов:
По сути мы просто меняем каждую цифру из числа по логике («там где был 0 - ставим 1, а там где была 1 - ставим 0»)
Может возникнуть резонный вопрос: «Почему при
~0
мы получаем -1
(в десятичной), а не максимальное число (где все разряды - 1
)?»При инвертировании
00000000
, мы должны получить 11111111
, данное число мы и получаем. Все дело в том, как устроены отрицательные двоичные числа.Вот хорошее видео на эту тему: Binary: Plusses & Minuses (Why We Use Two's Complement) - Computerphile
«Битовый Сдвиг влево»:
<<
Битовый сдвиг влево двигает все цифры двоичного числа влево и добавляет в конец 0. Справа от данного оператора можно указать сколько именно битов нам нужно сдвинуть.
console.log(0b101); // 5
console.log(0b101 << 1); // 0b1010 -> 10
console.log(0b101); // 5
console.log(0b101 << 2); // 0b10100 -> 20
«Битовый Сдвиг вправо»:
>>
Побитовый сдвиг вправо сдвигает все цифры в числе на 1 ячейку вправо. Данный оператор дублирует первую цифру с левой части числа для того чтобы вставить ее в начало.
let num;
console.group('0b00000000000000000000000000000101')
num = 0b00000000000000000000000000000101;
console.log(num); // 5
console.log(num >> 1); // 2 => 0b00000000000000000000000000000010
console.log(num >> 2); // 1 => 0b00000000000000000000000000000001
console.groupEnd();
console.group('0b11111111111111111111111111111010')
num = -0b00000000000000000000000000000101; // 0b11111111111111111111111111111010 => -5
console.log(num); // -5
console.log(num >> 1); // -3 => 0b11111111111111111111111111111101
console.log(num >> 2); // -2 => 0b11111111111111111111111111111110
console.log(num >> 3); // -1 => 0b11111111111111111111111111111111
console.groupEnd();
«Беззнаковый битовый сдвиг вправо»:
>>>
Данный оператор применяют когда нам нужно просто добавить
0
в начало числа, неважно какой до этого был знак у двоичного числа. Отрицательные числа всегда становятся положительными после использования данного оператора:// Для положительных чисел ничего не поменялось
let num;
console.group('0b00000000000000000000000000000101')
num = 0b00000000000000000000000000000101;
console.log(num); // 5
console.log(num >> 1); // 2 => 0b00000000000000000000000000000010
console.log(num >> 2); // 1 => 0b00000000000000000000000000000001
console.groupEnd();
// Однако, для отрицательных чисел кое-что поменялось
console.group('0b11111111111111111111111111111010')
num = -0b00000000000000000000000000000101; // 0b11111111111111111111111111111010 => -5
console.log(num); // -5
console.log(num >>> 1); // 2147483645 => 0b01111111111111111111111111111101
console.log(num >>> 2); // 1073741822 => 0b00111111111111111111111111111110
console.log(num >>> 3); // 536870911 => 0b00011111111111111111111111111111
console.groupEnd();