1. Побітовий зсув ліворуч

Також у Java є 3 оператори побітового зсуву числа. Ви можете дуже просто зсунути всі біти числа на кілька позицій ліворуч або праворуч, якщо вам це, звичайно, потрібно.

Щоби зсунути біти числа ліворуч, слід використовувати оператор побітового зсуву ліворуч. Записують це так:

a << b

де а — число, біти якого зсуваємо, а b — число, що означає на скільки бітів ліворуч потрібно зсунути біти числа a. Водночас з правого краю до числа додаються нульові біти.

Приклади:

Приклад Результат
0b00000011 << 1
0b00000110
0b00000011 << 2
0b00001100
0b00000011 << 5
0b01100000
0b00000011 << 20
0b001100000000000000000000

Зсув на один розряд ліворуч дає той самий ефект, що і множення числа на 2.

Хочете помножити число на 16? 16 = 24. Отже, потрібно зсунути число на 4 розряди ліворуч.


2. Побітовий зсув праворуч

В аналогічний спосіб біти можна зсувати праворуч. Для цього використовують оператор побітового зсуву праворуч. Записують його так:

a >> b

де а — число, біти якого зсуваємо, а b — число, що означає на скільки бітів праворуч потрібно зсунути біти числа a.

Приклади:

Приклад Результат
0b11000011 >> 1
0b01100001
0b11000011 >> 2
0b00110000
0b11000011 >> 5
0b00000110
0b11000011 >> 20
0b00000000

Зсув на один розряд праворуч дає той самий ефект, що й ділення числа на 2 без остачі.

Водночас з лівого краю до числа додаються нульові біти, але не завжди!

Важливо!

Крайній лівий біт числа називається знаковим бітом: він зберігає 0, якщо число додатне, і 1, якщо від'ємне.

У разі зсуву бітів числа праворуч знаковий біт теж зміститься, і знак числа буде втрачено. Тому для від'ємних чисел (у яких крайній лівий біт дорівнює 1) цей біт спеціально зберігається. Під час зсуву бітів числа праворуч число доповнюється зліва бітом 0, якщо крайній лівий біт мав значення 0, і бітом 1, якщо крайній лівий біт мав значення 1.

Однак у прикладі вище цього не відбувається. Чому? Річ у тім, що цілочислові літерали мають тип int, і насправді 0b11111111 означає 0b00000000000000000000000011111111. Тобто крайній лівий біт дорівнює нулю.

Багатьох програмістів засмучує така «поведінка» зсуву праворуч, і вони хотіли б, щоб число завжди доповнювалося нульовими бітами. Тому в Java додали ще один оператор зсуву праворуч.

Записують його так:

a >>> b

де а — число, біти якого зсуваємо, а b — число, що означає на скільки бітів праворуч потрібно зсунути біти числа a. Цей оператор завжди додає зліва нульові біти, незалежно від того, який знаковий біт мало число a.



3. Робота з прапорами

На основі побітових операцій та операцій зсуву програмісти реалізували мало не цілий новий напрям — роботу з прапорами.

За часів, коли пам'яті в комп'ютерах було обмаль, дуже популярним було впихати в одне число купу інформації. За таких обставин число розглядалося як масив бітів: int — це 32 біти, long — 64 біти.

У таке число можна багато всього записати, особливо якщо потрібно зберігати логічну інформацію: true або false. Одне число типу long — це як масив boolean на 64 елементи. Такі біти (елементи масиву) мали назву прапори й потребували операцій:

  • встановити прапор
    (зробити певний біт рівним 1)
  • скинути прапор
    (зробити певний біт рівним 0)
  • перевірити прапор
    (перевірити, чому дорівнює певний біт)

Отак це робиться за допомогою побітових операторів.

Встановлення прапора

Щоб установити певний біт в 1, потрібно виконати операцію побітового АБО між числом, в якому треба встановити цей біт, і спеціальним числом, де тільки цей біт дорівнює 1.

Наприклад, ви хочете в числі 0b00001010 установити 5-й біт в 1. Тоді потрібно зробити так:

0b00001010 | 0b00010000 = 0b00011010

Якщо 5-й біт уже було встановлено в одиницю, нічого не зміниться.

У загальному вигляді операцію встановлення прапора можна записати так:

a | (1 << b)

де a — це число, всередині якого певний біт установлюється в 1, а b — це індекс встановлюваного біта. Дуже зручно тут використовувати зсув ліворуч — одразу видно, з яким бітом ми працюємо.

Скидання прапора

Щоб скинути певний біт в 0 і не зачепити інші біти, потрібно виконати операцію & між числом, в якому потрібно скинути біт в 0, і спеціальним числом, де всі біти дорівнюють 1, окрім заданого.

Наприклад, ви хочете в числі 0b00001010 установити 4-й біт в 0. Тоді потрібно зробити так:

0b00001010 & 0b11110111 = 0b00000010

Якщо 4-й біт уже було скинуто в нуль, нічого не зміниться.

У загальному вигляді операцію скидання прапора можна записати так:

a & ~(1 << b)

де a — це число, всередині якого скидається в 0 певний біт, а b — це індекс скиданого біта.

Щоб отримати число, в якому всі біти дорівнюють 1, а потрібний нам дорівнює нулю, ми спочатку зсуваємо одиницю на b позицій ліворуч, а потім інвертуємо біти числа за допомогою побітової операції НЕ.

Перевірка прапора

Окрім встановлення або скидання певного прапора, іноді є потреба просто перевірити, чи встановлено цей прапор, тобто, чи дорівнює певний біт 1. Це досить легко зробити за допомогою побітової операції &.

Наприклад, ви хочете перевірити, чи встановлено 4-й біт в числі 0b00001010 в 1. Тоді потрібно зробити так:

if ( (0b00001010 & 0b00001000) == 0b00001000 )

У загальному вигляді операцію перевірки прапора можна записати так:

(a & (1 << b)) == (1 << b)

де a — це число, всередині якого перевіряється біт, а b — це індекс біта, який перевіряють.


4. Шифрування

Побітову операцію XOR програмісти часто застосовують для найпростішого шифрування. У загальному випадку операція шифрування має такий вигляд:

результат = число ^ пароль;

де число — це дані, які ми хочемо зашифрувати, пароль — спеціальне число, використовуване як «пароль» до даних, а результат — зашифроване число.

число == (число ^ пароль) ^ пароль;

Річ у тім, що оператор XOR, двічі застосований до числа, дає початкове число, незалежно від пароля.

Щоб отримати шукане число із зашифрованого, потрібно просто виконати операцію повторно:

вихідне число = результат ^ пароль;

Приклад:

class Solution
{
   public static int[] cript(int[] data, int password)
   {
     int[] result = new int[data.length];
     for (int i = 0; i <  data.length; i++)
       result[i] = data[i] ^ password;
     return result;
   }

   public static void main(String[] args)
   {
     int[] data =  {1, 3, 5, 7, 9, 11};
     int password = 199;

     // зашифровуємо масив даних
     int[] encrypted = cript(data, password) ;
     System.out.println(Arrays.toString(encrypted));

     // розшифровуємо масив даних
     int[] decrypted = cript(encrypted, password) ;
     System.out.println(Arrays.toString(decrypted));
   }
}