JavaRush /Курси /Java Syntax Zero /Побітові операції, частина 2

Побітові операції, частина 2

Java Syntax Zero
Рівень 9 , Лекція 6
Відкрита

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));
   }
}

Коментарі (16)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
11 грудня 2024
після деякого досвіду з ардуінами і С ця тема не вигляжає складною. але в С на скільки памятаю можна в квадратних скобках отримувати доступ прямо до бітів. тут трохи треба заморочуватися з конструкціями побітового зсуву. по правді сумніваюсь що в джаві комусь треба буде працювати з бітами, але для загального розвитку файно
ivan Рівень 10
21 вересня 2024
Нічого не зрозумів взагалі... що треба зробити у завдані??
Гаркін Рівень 14
20 березня 2024
До відома тих, хто буде проходити курс у подальшому. Bit numbering within a byte goes from most-significant bit (bit 0) to least-significant bit (bit n). This differs from some other big-endian processors. Тобто , 1 << flagPos це переміщення одиниці з біту за індексом 0 до біту з індексом flagPos .
Anonymous #3262541 Рівень 12
3 травня 2023
Степінь двійки:

return 1 << power;
Yaroslav Tkachyk Рівень 23 Expert
24 грудня 2022
Дописавши в останній задачі в IDEA: psvm+press[Tab]; sout+press[Tab]+setFlag(1,2); sout+press[Tab]+resetFlag(12,3); sout+press[Tab]+checkFlag(7,2); в мене виникло декілька запитань: Питання №1: String text = "З відеолекції 1 Harvard CS50 - 128(8-ий біт) 64(7-ий біт) 32(6-ий біт) 16(5-ий біт) 8(4-ий біт) 4(3-ій біт) 2(2-ий біт) 1(1-ий біт)" if (text == false) break; else Питання №2: Чому при компіляції коду в ІDEA - мені видає не ті результати, які я розраховував побачити? :) По порядку: setFlag(1,2) 00000001 | (00000001 << 2 ) = 00000101 (число 5) // (00000001 << 2) == 00000100 resetFlag(12,3) 00001100 & ~(00000001 << 3) = 00001000 // (00000001 << 3) == 00000100 (число 4) // ~(00001000) == 11110111 checkFlag(7,2) (00000111 & (00000001 << 2 )) == (00000001 << 2 ) (true) // (00000001 << 2) == 00000100 // 00000111 & 00000100 = 00000100 Коли почав писати про setFlag зрозумів як працює :) Я вважав, що число b (індекс біта / flagPos) - це номер біта результату, Приклад з resetFlag: 00001100 - це 12 я думав, що якщо flagPos = 3, тоді результат буде 00001[0]00 - це 8, але ні - правильний варіант описаний вище :) До речі - чи можна вважати число b+1 (flagPos+1 в задачі) - як номер біта (прапорця) результату, над яким буде проводитись операція (встановлення, скидання, перевірка)? По аналогії: setFlag(1,2) 8 7 6 5 4 3 2 1 - номер біта 0 0 0 0 0 0 0 1 - це 1 0 0 0 0 0 [1] 0 1 - це 5 в [] - ,біт №3, над яким проводилась операція (встановлення прапорця) = flagPos+1 Чи існують винятки? :)
Roma Chernesh Рівень 16
22 грудня 2022
Відчуття, що ця тема дана для "загального розвитку" і ніколи на реальній роботі не знадобиться
9 грудня 2022
Дуже важко сприймати цю тему. Кілька разів перечитувала, все рівно не докінця зрозуміло (((
Unfo Рівень 23
19 грудня 2022
якщо не дуже любив математику в принципі - тему можна читать и 8 разів, не зрозуміти половину. таке життя
kalkulator¹ Рівень 51
3 листопада 2022
останню задачу зробив методом ctrl c, cntr v a = number b = flagPos
Kostiantyn Bogatyrchuk Рівень 14 Expert
28 січня 2023
аналогічно, при тому , що ніфіга не зрозумів теми
Саша Рівень 30
9 жовтня 2022
в першій задачі помилка при виклику правильного розв'язку.
Roman Рівень 13
20 січня 2023
яка? немає помилки,потрібно правильно розвязати!!!
Shutstoboy Рівень 11
25 липня 2022
Складна тема, мізки закипають :с