Треки
/
Python
Python
/
Салабус
/
Побітові оператори
По

Побітові оператори у Python

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Побітові оператори

На рівні апаратного забезпечення транзистори можуть бути лише увімкненими або вимкненими: два стани, які ми традиційно позначаємо як 1 і 0. Це binary digits, скорочено bits. Розуміння bits і binary особливо важливе для системних програмістів, які працюють із низькорівневими мовами.

Однак протягом більшої частини історії обчислювальної техніки пріоритетом програмування був пошук дедалі складніших способів абстрагуватися від цієї двійкової реальності. У Python (і багатьох інших високорівневих мовах програмування) ми працюємо з int, float, string та іншими визначеними типами, аж до аудіо- та відеоформатів. Ми дозволяємо внутрішнім механізмам Python (зрештою) подбати про те, щоб перетворити все на біти.

Тим не менш, використання побітових операторів і побітових операцій іноді може дати значні переваги у швидкості та ефективності використання памʼяті, навіть у такій високорівневій мові, як Python.

Введення та відображення двійкових чисел

Як не дивно, Python взаємодіє з користувачем за допомогою десяткових чисел, але програміст може змінити цю типову поведінку. Насправді Python охоче приймає int у binary, hexadecimal або octal форматі й так само охоче виконує математичні операції між ними. Щоб дізнатися більше, можна переглянути концепцію binary-octal-hexadecimal.

Двійкові числа вводяться з префіксом 0b, так само як 0x можна використати для шістнадцяткових чисел (шістнадцяткові числа - стислий спосіб подати групи з 4 бітів), а oct для вісімкових чисел.

Існує кілька способів перетворити цілі числа на двійкові рядки тексту (англ. string), які різняться тим, чи містять вони префікс 0b і чи підтримують доповнення нулями зліва:

# Binary entry.
>>> 0b10111
23

# Converting an int display to binary string, with prefix.
>>> bin(23)  
'0b10111'

>>> number = 23

# Binary without prefix, padded to 8 digits.
>>> format(number, '08b')  
'00010111'

# Same format, but using an f-string.
>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex" 
'23 in decimal is 00010111 in binary and 17 in hex'

Bitwise Logic

У концепції bools ми обговорювали логічні оператори and, or і not, які використовують із булевими значеннями (англ. Boolean), такими як True і False. Ті самі правила логіки діють і під час роботи з бітами.

Однак побітові відповідники логічних операторів & (and), | (or), ~ (not) і ^ (XOR) застосовуються до кожного біта у двійковому поданні, трактуючи 1 як True («увімкнено»), а 0 як False («вимкнено»). Приклад із побітовим & допоможе це зрозуміти:

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x & y, '08b')
'00100010'

Лише ті позиції, у яких 1 стоїть в обох вхідних числах, мають 1 у вихідних даних.

Побітовий & часто використовують, щоб виділити окремі біти в упакованому наборі значень True/False, як-от налаштування застосунку, які може змінювати користувач. Це дає змогу керувати логікою програми за допомогою значень окремих бітів:

>>> number = 0b0110
>>> number & 0b0001 > 0
False

>>> number & 0b0010 > 0
True

Для побітового | (or) у вихідних даних встановлюється 1, якщо 1 є в хоч одному з входів:

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x | y, '08b')
'01101110'

Оператор ^ відповідає побітовому виключному або (xor): 1 встановлюється, якщо він є в одному з двох входів, але не в обох. Цей символ може здатися знайомим із концепції sets, де його використовують для симетричної різниці set, яка збігається з XOR, застосованим до множин. Якщо xor ^ здається дивним, знайте: це найпоширеніша операція в криптографії.

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x ^ y, '08b')
'01001100'

Нарешті, є оператор ~ (символ тильда), тобто побітове not, яке приймає один вхід і інвертує всі біти, а результат може виявитися не таким, як ми очікували! Кожен 1 у поданні змінюється на 0, і навпаки. Докладніше про це йдеться в розділі нижче.

Відʼємні числа та двійкове подання

У десятковому поданні ми розрізняємо додатні та відʼємні числа за допомогою знака + або - ліворуч від цифр. Використання цих символів на двійковому рівні виявилося неефективним для цифрових обчислень і породило проблему: +0 не те саме, що -0.

Замість використання - і + всі сучасні компʼютери застосовують для відʼємних чисел подання two's complement, аж до рівня кремнієвих чипів. Це означає, що всі біти інвертуються, а число інтерпретується як відʼємне, якщо найлівіший біт (його ще називають «старшим бітом», або MSB) дорівнює 1. У додатних чисел старший біт дорівнює 0. Це подання має ту перевагу, що нуль існує лише в одній версії, тож програмісту не доводиться мати справу з -0 і +0.

Такий спосіб подання відʼємних і додатних чисел додає складнощів для Python: у самій мові, в її ядрі, немає понять цілих чисел фіксованого розміру, як-от int32 чи int64. У «сучасному» Python int мають необмежений розмір (обмежений лише можливостями апаратного забезпечення), і відʼємне чи побітово інвертоване число має (теоретично) нескінченну кількість 1 ліворуч, так само як додатне число має необмежену кількість 0.

Через це складно навести змістовний приклад bitwise not:

>>> x = 0b01100110
>>> format(x, '08b')
'01100110'

# This is a negative binary (not twos-complement display).
>>> format(~x, '08b')
'-1100111'  

 # Decimal representation.
>>> x
102

# Using the Bitwise not, with an unintuitive result.
>>> ~x
-103

Це не той 0b10011001, який ми побачили б у мовах із цілими числами фіксованого розміру.

Оператор ~ працює як очікується лише з беззнаковими байтовими чи цілочисельними типами або з типами цілих чисел фіксованого розміру. Ці числові типи підтримуються в сторонніх пакетах, як-от NumPy, pandas і sympy, але не в ядрі Python.

На практиці програмісти Python досить часто використовують &, |, ^ і оператори зсуву, описані нижче, лише з додатними числами. Побітові операції з відʼємними числами трапляються набагато рідше. Один прийом - додати 2**32 (or 1 << 32) до відʼємного значення, щоб зробити int беззнаковим, але цим стає важко керувати. Інша стратегія - працювати з модулем ctypes і використовувати цілочисельні типи в стилі C, але це так само незручно.

Shift operators

Оператор зсуву вліво x << y переміщує всі біти в x на y позицій уліво, заповнюючи нові проміжки нулями. Зауважмо, що арифметично це те саме, що помножити число на (2**y).

Оператор зсуву вправо x >> y робить протилежне. Арифметично це те саме, що цілочисельне ділення x // (2**y).

Памʼятаймо про попередній розділ, присвячений відʼємним числам, і про пастки, які чатують на них під час зсуву в Python.

>>> x = 8
>>> format(x, '08b')
'00001000'

# A left bit shift. 
>>> x << 2  
32

>>> format(x << 2, '08b')
'00100000'

# A right bit shift. 
>>> format(x >> 2, '08b')
'00000010'
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці