Які є випадки використання реального світу наступних побітових операторів?
- І
- XOR
- НЕ
- АБО
- Зсув вліво / вправо
Які є випадки використання реального світу наступних побітових операторів?
Відповіді:
Поля бітів (прапори)
Вони є найефективнішим способом представлення того, стан якого визначається декількома властивостями "так чи ні". ACL - хороший приклад; якщо ви скажімо 4 дискретних дозволу (читати, записувати, виконувати, змінювати політику), краще зберігати це в 1 байті, а не відходах 4. Вони можуть бути відображені на типи перерахування на багатьох мовах для додаткової зручності.
Комунікація через порти / сокети
Завжди включає контрольні суми, парність, стоп-біти, алгоритми управління потоком тощо, які зазвичай залежать від логічних значень окремих байтів на відміну від числових значень, оскільки носій може бути здатний передавати лише один біт на Час.
Стиснення, шифрування
Обидва вони сильно залежать від бітових алгоритмів. Подивіться на приклад алгоритм дефляції - все в бітах, а не в байтах.
Машини з кінцевим
станом, я кажу в основному про тип, вбудований в якийсь апарат, хоча вони можуть бути знайдені і в програмному забезпеченні. Це комбінаторний характер - вони можуть буквально отримувати «скомпільований» вниз на купу логічних вентилів, тому вони повинні бути виражені як AND
, OR
, NOT
і т.д.
Графіка
Тут навряд чи достатньо місця, щоб потрапити в кожну область, де ці оператори використовуються в графічному програмуванні. XOR
(або ^
) тут особливо цікаво, оскільки застосування одного і того ж вводу вдруге скасує перший. Старі графічні інтерфейси використовували для цього посилання на виділення виділень та інші накладки, щоб усунути необхідність у дорогому перемальовуванні. Вони все ще корисні у повільних графічних протоколах (тобто віддаленому робочому столі).
Це були лише перші кілька прикладів, які я придумав - це навряд чи вичерпний список.
Це дивно?
(value & 0x1) > 0
Це ділиться на два (парні)?
(value & 0x1) == 0
Ось деякі поширені ідіоми, що стосуються прапорів, що зберігаються як окремі біти.
enum CDRIndicators {
Local = 1 << 0,
External = 1 << 1,
CallerIDMissing = 1 << 2,
Chargeable = 1 << 3
};
unsigned int flags = 0;
Встановіть прапор, що підлягає сплаті:
flags |= Chargeable;
Очистити прапор CallerIDМіссінг:
flags &= ~CallerIDMissing;
Перевірте, чи встановлено CallerIDMissing та Chargeable:
if((flags & (CallerIDMissing | Chargeable )) == (CallerIDMissing | Chargeable)) {
}
Я використовував побітні операції при впровадженні моделі безпеки для CMS. Він мав сторінки, до яких користувачі могли отримати доступ, якщо вони були у відповідних групах. Користувач може бути у кількох групах, тому нам потрібно було перевірити, чи не було перетину між групами користувачів та групами сторінок. Тому ми призначили кожній групі унікальний ідентифікатор потужності-2, наприклад:
Group A = 1 --> 00000001
Group B = 2 --> 00000010
Group C = 3 --> 00000100
Ми АБО ці значення разом і зберігаємо значення (як єдине ціле) зі сторінкою. Наприклад, якщо до сторінки можна отримати доступ групами A&B, ми зберігаємо значення 3 (яке у двійковій формі є 00000011) як контроль доступу до сторінок. Таким же чином ми зберігаємо значення ідентифікаторів групи ORed з користувачем, щоб представити, в яких групах вони перебувають.
Отже, щоб перевірити, чи може певний користувач отримати доступ до певної сторінки, вам потрібно просто разом зіставити значення І і перевірити, чи значення не дорівнює нулю. Це дуже швидко, оскільки ця перевірка реалізована в одній інструкції, без циклу, без обходу баз даних.
Хороший приклад - програмування низького рівня. Наприклад, вам може знадобитися записати певний біт в регістр, нанесений на пам'ять, щоб змусити частину обладнання зробити те, що вам потрібно:
volatile uint32_t *register = (volatile uint32_t *)0x87000000;
uint32_t value;
uint32_t set_bit = 0x00010000;
uint32_t clear_bit = 0x00001000;
value = *register; // get current value from the register
value = value & ~clear_bit; // clear a bit
value = value | set_bit; // set a bit
*register = value; // write it back to the register
Крім того , htonl()
і htons()
реалізуються з використанням &
і |
операторів (на машинах , у яких порядок байт (Byte замовлення) не відповідає мережевий замовлення):
#define htons(a) ((((a) & 0xff00) >> 8) | \
(((a) & 0x00ff) << 8))
#define htonl(a) ((((a) & 0xff000000) >> 24) | \
(((a) & 0x00ff0000) >> 8) | \
(((a) & 0x0000ff00) << 8) | \
(((a) & 0x000000ff) << 24))
htons()
і htonl()
є функціями POSIX для заміни a short
або a long
з h
нестабільності хоста ( ) на порядок байтів мережі ( n
).
htonl()
для 32-бітного int
значення? long
означає 64-бітові багато мов.
Наприклад, я використовую їх для отримання значень RGB (A) від упакованих кольорових кольорів.
(a & b) >> c
більш ніж у 5 разів швидше, ніж a % d / e
(обидва способи витягти єдине значення кольору з int, що представляє ARGB). Відповідно, 6,7 та 35,2 за 1 мільярд ітерацій.
%
не є оператором Modulus, це оператором Залишком. Вони еквівалентні позитивним значенням, але відрізняються від негативних. Якщо ви надаєте відповідні обмеження (передаючи а uint
замість, int
наприклад), то два приклади повинні мати однакову швидкість.
Коли у мене є купа булевих прапорів, я люблю зберігати їх у int.
Я виймаю їх, використовуючи побіто-І. Наприклад:
int flags;
if (flags & 0x10) {
// Turn this feature on.
}
if (flags & 0x08) {
// Turn a second feature on.
}
тощо.
if (flags.feature_one_is_one) { // turn on feature }
. Це в стандарті ANSI C, тому портативність не повинна бути проблемою.
& = І:
маскуйте конкретні біти.
Ви визначаєте конкретні біти, які слід відображати чи не відображати. 0x0 & x очистить всі біти в байті, тоді як 0xFF не змінить x. 0x0F відобразить біти в нижній області.
Перетворення:
Для передачі більш коротких змінних у більш довгі з бітовою ідентичністю слід відрегулювати біти, оскільки -1 в інті 0xFFFFFFFF, тоді як -1 в довгому 0xFFFFFFFFFFFFFFFFFF. Для збереження ідентичності ви застосовуєте маску після перетворення.
| = АБО
Встановити біти. Біти будуть встановлені самостійно, якщо вони вже встановлені. У багатьох структурах даних (бітових полях) є прапори, такі як IS_HSET = 0, IS_VSET = 1, які можна встановити самостійно. Щоб встановити прапори, ви застосуєте IS_HSET | IS_VSET (В монтажі C і це дуже зручно читати)
^ = XOR
Знайдіть біти, однакові чи різні.
~ = НЕ
фліп біт.
Можна показати, що всі можливі локальні бітові операції можуть бути реалізовані цими операціями. Отже, якщо вам подобається, ви можете реалізувати інструкцію ADD виключно за допомогою бітових операцій.
Деякі чудові хаки:
http://www.ugcs.caltech.edu/~wnoise/base2.html
http://www.jjj.de/bitwizardry/bitwizardrypage.html
= ~
, ні |=
, що АБО.
& = AND
- Чому я хотів би очистити всі біти, чому я хочу отримати немодифіковану версію байта, і що мені робити з нижньою нибл?
xor
це сам по собі. Я можу подумати про чимало причин, з яких ви можете витягнути нижню жилку. Особливо, якщо цей нижній нібіл є частиною структури даних, і ви хочете використовувати його як маску або OR
іншу структуру.
Шифрування - це все побітові операції.
Ви можете використовувати їх як швидкий і брудний спосіб хеш-даних.
int a = 1230123;
int b = 1234555;
int c = 5865683;
int hash = a ^ b ^ c;
Це приклад для читання кольорів із растрового зображення у байтовому форматі
byte imagePixel = 0xCCDDEE; /* Image in RRGGBB format R=Red, G=Green, B=Blue */
//To only have red
byte redColour = imagePixel & 0xFF0000; /*Bitmasking with AND operator */
//Now, we only want red colour
redColour = (redColour >> 24) & 0xFF; /* This now returns a red colour between 0x00 and 0xFF.
Я сподіваюся, що це крихітні приклади допомагають….
У абстрагованому світі сучасної сучасної мови не дуже багато. Файл IO - це легкий, який спадає на думку, хоча він виконує побітові операції на вже реалізованому і не реалізує щось, що використовує побітові операції. Однак, як простий приклад, цей код демонструє видалення атрибута лише для читання з файлу (щоб його можна було використовувати з новим FileStream із зазначенням FileMode.Create) у c #:
//Hidden files posses some extra attibutes that make the FileStream throw an exception
//even with FileMode.Create (if exists -> overwrite) so delete it and don't worry about it!
if(File.Exists(targetName))
{
FileAttributes attributes = File.GetAttributes(targetName);
if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
File.SetAttributes(targetName, attributes & (~FileAttributes.ReadOnly));
File.Delete(targetName);
}
Що стосується спеціальних реалізацій, ось останній приклад: я створив "центр повідомлень" для надсилання захищених повідомлень з однієї установки нашого розповсюдженого додатку на іншу. В основному, він є аналогом електронної пошти в комплекті з Inbox, Outbox, Sent і т. Д., Але він також має гарантовану доставку з квитанціями про зчитування, тому є додаткові папки за межами "Вхідні" та "Надіслані". З цього приводу я мав вимогу загально визначити, що "у папці" Вхідні "або що" у надісланій папці ". З надісланої папки мені потрібно знати, що читати, а що не читати. З того, що є непрочитаним, мені потрібно знати, що отримано, а що - не. Я використовую цю інформацію для створення динаміки, де застереження, яке фільтрує локальний джерело даних та відображає відповідну інформацію.
Ось як складається перерахунок:
public enum MemoView :int
{
InboundMemos = 1, // 0000 0001
InboundMemosForMyOrders = 3, // 0000 0011
SentMemosAll = 16, // 0001 0000
SentMemosNotReceived = 48, // 0011
SentMemosReceivedNotRead = 80, // 0101
SentMemosRead = 144, // 1001
Outbox = 272, //0001 0001 0000
OutBoxErrors = 784 //0011 0001 0000
}
Ви бачите, що це робить? По anding (&) зі значенням перерахунку "Inbox", InboundMemos, я знаю, що InboundMemosForMyOrders знаходиться у папці "Вхідні".
Ось приведена версія методу, який створює та повертає фільтр, який визначає подання для вибраної в даний час папки:
private string GetFilterForView(MemoView view, DefaultableBoolean readOnly)
{
string filter = string.Empty;
if((view & MemoView.InboundMemos) == MemoView.InboundMemos)
{
filter = "<inbox filter conditions>";
if((view & MemoView.InboundMemosForMyOrders) == MemoView.InboundMemosForMyOrders)
{
filter += "<my memo filter conditions>";
}
}
else if((view & MemoView.SentMemosAll) == MemoView.SentMemosAll)
{
//all sent items have originating system = to local
filter = "<memos leaving current system>";
if((view & MemoView.Outbox) == MemoView.Outbox)
{
...
}
else
{
//sent sub folders
filter += "<all sent items>";
if((view & MemoView.SentMemosNotReceived) == MemoView.SentMemosNotReceived)
{
if((view & MemoView.SentMemosReceivedNotRead) == MemoView.SentMemosReceivedNotRead)
{
filter += "<not received and not read conditions>";
}
else
filter += "<received and not read conditions>";
}
}
}
return filter;
}
Надзвичайно проста, але акуратна реалізація на рівні абстракції, яка зазвичай не вимагає побітних операцій.
Кодування Base64 - приклад. Кодування Base64 використовується для представлення двійкових даних у вигляді символів для друку для надсилання електронної пошти (та інших цілей). Кодування Base64 перетворює серію 8-бітових байтів у 6-бітові індекси пошуку символів. Бітові операції, зсув, і'ing, або 'oring, not'ing дуже корисні для реалізації бітових операцій, необхідних для кодування та декодування Base64.
Це звичайно лише 1 із незліченних прикладів.
Я здивований, що ніхто не вибрав очевидну відповідь за епоху Інтернету. Розрахунок дійсних мережевих адрес для підмережі.
Зазвичай бітові операції швидше, ніж робити множення / ділення. Отже, якщо вам потрібно помножити змінну x на сказати 9, ви зробите x<<3 + x
це на кілька циклів швидше, ніж x*9
. Якщо цей код знаходиться в межах ISR, ви заощадите на час відповіді.
Аналогічно, якщо ви хочете використовувати масив у якості кругової черги, було б швидше (і більш елегантніше) обробляти обертання чеків за допомогою трохи розумних операцій. (розмір вашого масиву повинен бути потужністю 2). Наприклад: ви можете використовувати tail = ((tail & MASK) + 1)
замість tail = ((tail +1) < size) ? tail+1 : 0
, якщо ви хочете вставити / видалити.
Крім того, якщо ви хочете, щоб прапор помилки містив кілька кодів помилок разом, кожен біт може містити окреме значення. Ви можете І з кожним індивідуальним кодом помилки як перевірку. Це використовується в кодах помилок Unix.
Також n-бітова растрова карта може бути дійсно крутою і компактною структурою даних. Якщо ви хочете виділити пул ресурсів розміром n, ми можемо використовувати n-біти для представлення поточного стану.
Здається, ніхто не згадав математику з фіксованою точкою.
(Так, я старий, добре?)
Чи число x
є потужністю 2? (Корисно, наприклад, в алгоритмах, де лічильник збільшується, а дію потрібно вжити лише логарифмічну кількість разів)
(x & (x - 1)) == 0
Який найвищий біт цілого числа x
? (Це, наприклад, можна знайти мінімальну потужність на 2, що перевищує x
)
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return x - (x >>> 1); // ">>>" is unsigned right shift
Який найнижчий 1
біт цілого числа x
? (Допомагає знайти кількість разів, що ділиться на 2.)
x & -x
x & -x
.
Побітові оператори корисні для циклічних масивів, довжина яких становить 2 сили. Як багато хто згадує, побітові оператори надзвичайно корисні і використовуються у прапорах , графіці , мережах , шифруванні . Мало того, але вони надзвичайно швидкі. Мій особистий фаворит використання в циклі масив без умовними . Припустимо, у вас є масив на основі нуля-індексу (наприклад, індекс першого елемента дорівнює 0), і вам потрібно циклічно його циклічно. Під невизначеним терміном я маю на увазі перехід від першого елемента до останнього і повернення до першого. Одним із способів здійснення цього є:
int[] arr = new int[8];
int i = 0;
while (true) {
print(arr[i]);
i = i + 1;
if (i >= arr.length)
i = 0;
}
Це найпростіший підхід, якщо ви хочете уникнути, якщо заява, ви можете використовувати модульний підхід таким чином:
int[] arr = new int[8];
int i = 0;
while (true) {
print(arr[i]);
i = i + 1;
i = i % arr.length;
}
Нижньою стороною цих двох методів є те, що оператор модуля є дорогим, оскільки шукає залишок після цілого поділу. І перший метод запускає оператор if на кожній ітерації. Однак при бітовому операторі, якщо довжина масиву є потужністю 2, ви можете легко генерувати послідовність на зразок 0 .. length - 1
, використовуючи &
(бітовий та) оператор i & length
. Знаючи це, код зверху стає
int[] arr = new int[8];
int i = 0;
while (true){
print(arr[i]);
i = i + 1;
i = i & (arr.length - 1);
}
Ось як це працює. У двійковому форматі кожне число, яке є потужністю 2, віднімається на 1, виражається лише одиницею. Наприклад, 3 у двійковій 11
, 7 є 111
, 15 є 1111
і так далі, ви отримуєте думку. Тепер, що станеться, якщо &
будь-яке число проти числа, що складається лише з числа у двійковому? Скажімо, ми робимо це:
num & 7;
Якщо num
вона менша або дорівнює 7, то результат буде num
тому, що кожен біт &
-ed з 1 є самим собою. Якщо num
більший за 7, під час &
роботи комп'ютер вважатиме провідними нулями 7, які, звичайно, залишатимуться нулями після &
роботи, залишиться лише задня частина. Як і у випадку з 9 & 7
двійковою формою, це буде виглядати
1001 & 0111
результатом буде 0001, який дорівнює 1 в десятковій частині, і адресує другий елемент у масиві.
це також може бути зручно у реляційній моделі sql, скажімо, у вас є такі таблиці: BlogEntry, BlogCategory
традиційно ви можете створити nn відносини між ними за допомогою таблиці BlogEntryCategory або коли таких записів BlogCategory не так багато, ви можете використовувати одне значення в BlogEntry для з'єднання з декількома записами BlogCategory так само, як ви б це зробили з позначеними перерахунками, в більшості RDBMS також є дуже швидкі оператори, щоб вибрати в цьому стовпчику "позначений" ...
Коли ви хочете лише змінити деякі біти виходів мікроконтролера, але реєстр, до якого потрібно записати, - це байт, ви робите щось подібне (псевдокод):
char newOut = OutRegister & 0b00011111 //clear 3 msb's
newOut = newOut | 0b10100000 //write '101' to the 3 msb's
OutRegister = newOut //Update Outputs
Звичайно, багато мікроконтролерів дозволяють змінювати кожен біт окремо ...
Якщо ви хочете коли-небудь обчислити число мод (%) певної потужності в 2, ви можете використовувати yourNumber & 2^N-1
, що в цьому випадку те саме, що yourNumber % 2^N
.
number % 16 = number & 15;
number % 128 = number & 127;
Це, мабуть, корисно лише в тому, що це альтернатива операції з модулем з дуже великим дивідендом, який становить 2 ^ N ... Але навіть тоді його підвищення швидкості над операцією модуля незначне в моєму тесті на .NET 2.0. Я підозрюю, що сучасні компілятори вже виконують такі оптимізації. Хтось знає більше про це?
%
і операція "Рештайндер", вони негативно ставляться до негативів. Однак якщо ви перейдете uint
до цього %
, компілятор C # фактично виробляє машинний код за допомогою побітового AND, коли другий аргумент - це загальновідома потужність двох.
У моєму запитанні є справжнє використання світу -
Відповісти лише на перше сповіщення WM_KEYDOWN?
При споживанні повідомлення WM_KEYDOWN у вікні C api біт 30 вказується попередній стан ключа. Значення дорівнює 1, якщо ключ відхилений до надсилання повідомлення, або він дорівнює нулю, якщо ключ вгору
Вони в основному використовуються для розрядних операцій (сюрприз). Ось кілька прикладів реального світу, знайдених у кодовій базі PHP.
Кодування символів:
if (s <= 0 && (c & ~MBFL_WCSPLANE_MASK) == MBFL_WCSPLANE_KOI8R) {
Структура даних:
ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;
Драйвери бази даних:
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
Реалізація компілятора:
opline->extended_value = (opline->extended_value & ~ZEND_FETCH_CLASS_MASK) | ZEND_FETCH_CLASS_INTERFACE;
Щоразу, коли я вперше розпочав програмування на C, я розумів таблиці істинності і все це, але не все клацнуло, як реально його використовувати, поки я не прочитав цю статтю http://www.gamedev.net/reference/articles/article1563.asp (що дає приклади реального життя)
x == 1
і y == 2
, тоді x || y
оцінюється до 1, а x | y
до 0. Я також не бачу, чому будь-який спосіб x^true
є кращим !x
. Це більше вводити текст, менш ідіоматично, а якщо x
не трапляється, bool
це ненадійно.
x^true
перевершує !x
є some->complicated().member->lookup ^= true;
Там немає версії складеного присвоювання унарних операторів.
Лінійне рішення Tower of Hanoi використовує розрядні операції для вирішення проблеми.
public static void linear(char start, char temp, char end, int discs)
{
int from,to;
for (int i = 1; i < (1 << discs); i++) {
from = (i & i-1) % 3;
to = ((i | i-1) + 1) % 3;
System.out.println(from+" => "+to);
}
}
Пояснення цього рішення можна знайти тут