С работа с битами: C/C++ Работа с битами — volstr.ru

Содержание

Побитовые операторы в C++: И, ИЛИ, НЕ и исключающее ИЛИ | Уроки С++

  Обновл. 11 Сен 2020  | 

Побитовые операторы манипулируют отдельными битами в пределах переменной.

Примечание: Для некоторых этот материал может показаться сложным. Если вы застряли или что-то не понятно — пропустите этот урок (и следующий), в будущем сможете вернуться и разобраться детально. Он не столь важен для прогресса в изучении языка C++, как другие уроки, и изложен здесь в большей мере для общего развития.

Зачем нужны побитовые операторы?

В далеком прошлом компьютерной памяти было очень мало и ею сильно дорожили. Это было стимулом максимально разумно использовать каждый доступный бит. Например, в логическом типе данных bool есть всего лишь два возможных значения (true и false), которые могут быть представлены одним битом, но по факту занимают целый байт памяти! А это, в свою очередь, из-за того, что переменные используют уникальные адреса памяти, а они выделяются только в байтах. Переменная bool занимает 1 бит, а другие 7 бит — тратятся впустую.

Используя побитовые операторы, можно создавать функции, которые позволят уместить 8 значений типа bool в переменную размером 1 байт, что значительно сэкономит потребление памяти. В прошлом такой трюк был очень популярен. Но сегодня, по крайней мере, в прикладном программировании, это не так.

Теперь памяти стало существенно больше и программисты обнаружили, что лучше писать код так, чтобы было проще и понятнее его поддерживать, нежели усложнять его ради незначительной экономии памяти. Поэтому спрос на использование побитовых операторов несколько уменьшился, за исключением случаев, когда необходима уж максимальная оптимизация (например, научные программы, которые используют огромное количество данных; игры, где манипуляции с битами могут быть использованы для дополнительной скорости; встроенные программы, где память по-прежнему ограничена).

В языке С++ есть 6 побитовых операторов:

Оператор Символ Пример Операция
Побитовый сдвиг влево << x << y Все биты в x смещаются влево на y бит
Побитовый сдвиг вправо >> x >> y Все биты в x смещаются вправо на y бит
Побитовое НЕ ~ ~x Все биты в x меняются на противоположные
Побитовое И & x & y Каждый бит в x И каждый соответствующий ему бит в y
Побитовое ИЛИ | x | y Каждый бит в x ИЛИ каждый соответствующий ему бит в y
Побитовое исключающее ИЛИ (XOR) ^ x ^ y Каждый бит в x XOR с каждым соответствующим ему битом в y

В побитовых операциях следует использовать только целочисленные типы данных unsigned, так как C++ не всегда гарантирует корректную работу побитовых операторов с целочисленными типами signed.

Правило: При работе с побитовыми операторами используйте целочисленные типы данных unsigned.

Побитовый сдвиг влево (<<) и побитовый сдвиг вправо (>>)

В языке C++ количество используемых бит основывается на размере типа данных (в 1 байте находятся 8 бит). Оператор побитового сдвига влево (<<) сдвигает биты влево. Левый операнд является выражением, в котором они сдвигаются, а правый — количество мест, на которые нужно сдвинуть. Поэтому в выражении 3 << 1 мы имеем в виду «сдвинуть биты влево в литерале 3 на одно место».

Примечание: В следующих примерах мы будем работать с 4-битными двоичными значениями.

Рассмотрим число 3, которое в двоичной системе равно 0011:

3 = 0011
3 << 1 = 0110 = 6
3 << 2 = 1100 = 12
3 << 3 = 1000 = 8

В последнем третьем случае один бит перемещается за пределы самого литерала! Биты, сдвинутые за пределы двоичного числа, теряются навсегда.

Оператор побитового сдвига вправо (>>) сдвигает биты вправо. Например:

12 = 1100
12 >> 1 = 0110 = 6
12 >> 2 = 0011 = 3
12 >> 3 = 0001 = 1

В третьем случае мы снова переместили бит за пределы литерала. Он также потерялся навсегда.

Хотя в примерах, приведенных выше, мы смещаем биты только в литералах, мы также можем смещать биты и в переменных:

unsigned int x = 4;
x = x << 1; // x должен стать равным 8

unsigned int x = 4;

x = x << 1; // x должен стать равным 8

Следует помнить, что результаты операций с побитовыми сдвигами в разных компиляторах могут отличаться.

Что!? Разве операторы << и >> используются не для вывода и ввода данных?

И для этого тоже.

Сейчас польза от использования побитовых операторов не так велика, как это было раньше. Сейчас в большинстве случаев оператор побитового сдвига влево используется для вывода данных. Например, рассмотрим следующую программу:

#include <iostream>

int main()
{
unsigned int x = 4;
x = x << 1; // оператор << используется для побитового сдвига влево
std::cout << x; // оператор << используется для вывода данных в консоль

return 0;
}

#include <iostream>

int main()

{

    unsigned int x = 4;

    x = x << 1; // оператор << используется для побитового сдвига влево

    std::cout << x; // оператор << используется для вывода данных в консоль

    return 0;

}

Результат выполнения программы:

8

А как компилятор понимает, когда нужно применить оператор побитового сдвига влево, а когда выводить данные? Всё очень просто. std::cout переопределяет значение оператора << по умолчанию на новое (вывод данных в консоль). Когда компилятор видит, что левым операндом оператора << является std::cout, то он понимает, что должен произойти вывод данных. Если левым операндом является переменная целочисленного типа данных, то компилятор понимает, что должен произойти побитовый сдвиг влево (операция по умолчанию).

Побитовый оператор НЕ

Побитовый оператор НЕ (~), пожалуй, самый простой для объяснения и понимания. Он просто меняет каждый бит на противоположный, например, с 0 на 1 или с 1 на 0. Обратите внимание, результаты побитового НЕ зависят от размера типа данных!

Предположим, что размер типа данных составляет 4 бита:

4 = 0100
~ 4 = 1011 (двоичное) = 11 (десятичное)

Предположим, что размер типа данных составляет 8 бит:

4 = 0000 0100
~ 4 = 1111 1011 (двоичное) = 251 (десятичное)

Побитовые операторы И, ИЛИ и исключающее ИЛИ (XOR)

Побитовые операторы И (&) и ИЛИ (|) работают аналогично логическим операторам И и ИЛИ. Однако, побитовые операторы применяются к каждому биту отдельно! Например, рассмотрим выражение 5 | 6. В двоичной системе это 0101 | 0110. В любой побитовой операции операнды лучше всего размещать следующим образом:

0 1 0 1 // 5
0 1 1 0 // 6

А затем применять операцию к каждому столбцу с битами по отдельности. Как вы помните, логическое ИЛИ возвращает true (1), если один из двух или оба операнды истинны (1). Аналогичным образом работает и побитовое ИЛИ. Выражение 5 | 6 обрабатывается следующим образом:

0 1 0 1 // 5
0 1 1 0 // 6
-------
0 1 1 1 // 7

Результат:

0111 (двоичное) = 7 (десятичное)

Также можно обрабатывать и комплексные выражения ИЛИ, например, 1 | 4 | 6. Если хоть один бит в столбце равен 1, то результат целого столбца — 1. Например:

0 0 0 1 // 1
0 1 0 0 // 4
0 1 1 0 // 6
--------
0 1 1 1 // 7

Результатом 1 | 4 | 6 является десятичное 7. 7. Если единиц в столбце чётное количество, то результатом будет 0, если же нечётное количество, то результат — 1. Например:

0 0 0 1 // 1
0 0 1 1 // 3
0 1 1 1 // 7
--------
0 1 0 1 // 5

Побитовые операторы присваивания

Как и в случае с арифметическими операторами присваивания, язык C++ предоставляет побитовые операторы присваивания для облегчения внесения изменений в переменные.

Оператор Символ Пример Операция
Присваивание с побитовым сдвигом влево <<= x <<= y Сдвигаем биты в x влево на y бит
Присваивание с побитовым сдвигом вправо >>= x >>= y Сдвигаем биты в x вправо на y бит
Присваивание с побитовой операцией ИЛИ |= x |= y Присваивание результата выражения x | y переменной x
Присваивание с побитовой операцией И &= x &= y Присваивание результата выражения x & y переменной x
Присваивание с побитовой операцией исключающего ИЛИ ^= x ^= y Присваивание результата выражения x ^ y переменной x

Например, вместо х = х << 1; мы можем написать х <<= 1;. 12:

0 1 0 1
1 1 0 0
--------
1 0 0 1 // 9 (десятичное)

Оценить статью:

Загрузка…

Поделиться в социальных сетях:

битовые операции — Биты на C++ на примере кода.

@Atom, отвечаю на вопросы:

1. const int S=8*sizeof(int)-1; — узнаем сколько бит нужно для целого без знакового типа так?

Нет. Количество бит в int это S+1, а S это максимальный номер бита в int.

2. const int M=1<<S; — сдвинули 32 бита влево на 1 бит, если да то почему мы сдвигаем биты, почему не оставляем их на месте, как это понять?

Да, сдвигаем все биты влево, бит 0 становится нулем. Например

x: 10010000 00001000 10101111 100000001 после x<<=1 превратится в
   00100000 00010001 01011111 000000010

Конкретно для нашего примера с S и М

S=8*sizeof(int)-1; // S = 31
M=1<<S;  
1:     00000000 00000000 00000000 00000001
1<<31: 10000000 00000000 00000000 00000000

Почему мы не оставляем биты на месте? Очень просто, нам это не нужно.

Что мы хотим на самом деле? Мы хотим узнать значение бита целого числа в позиции n (например 2). Для этого мы хотим использовать битовую операцию AND (операция & в C/C++).

v = 266  :    00000000 00000000 00000001 00001010 // видно, что бит 2 это 0
1        :    00000000 00000000 00000000 00000001 // число 1, будем делать 1 << 2
M = 1<<2 :    00000000 00000000 00000000 00000100 // сдвинули
v & M    :    00000000 00000000 00000000 00000000 // число 0 т.е. бит 2 был 0

3. x<<=1 if(x&M) { cout<<1;} else {cout<<0;}

      for (int i = 0, x = 1; i < sizeof(v)*8; i++, x <<= 1)
          if (v & x)
              cout << "bit "<<i<<" is set\n";

Это печать какие биты числа v установлены (в 1). По шагам:

x = 1,  i = 0, v = 266
v:    00000000 00000000 00000001 00001010
x:    00000000 00000000 00000000 00000001
v&x:  00000000 00000000 00000000 00000000 // ничего не печатаем
x << 1, i = 1, v = 266
v:    00000000 00000000 00000001 00001010
x:    00000000 00000000 00000000 00000010
v&x:  00000000 00000000 00000000 00000010 // cout << "bit 1 is set"
x << 1, i = 2, v = 266 
v:    00000000 00000000 00000001 00001010
x:    00000000 00000000 00000000 00000100
v&x:  00000000 00000000 00000000 00000000 // ничего не печатаем
x << 1, i = 3, v = 266
v:    00000000 00000000 00000001 00001010
x:    00000000 00000000 00000000 00001000
v&x:  00000000 00000000 00000000 00001000 // cout << "bit 3 is set"

и т.д. Все 32 шага расписывать не буду. Надеюсь теперь понятно. Что-то еще захотите уточнить — спрашивайте.

PostgreSQL : Документация: 9.6: 9.6. Функции и операторы для работы с битовыми строками : Компания Postgres Professional

9.6. Функции и операторы для работы с битовыми строками

В этом разделе описываются функции и операторы, предназначенные для работы с битовыми строками, то есть с данными типов bit и bit varying. Помимо обычных операторов сравнения, с такими данными можно использовать операторы, перечисленные в Таблице 9.13. Заметьте, что операторы &, | и # работают только с двоичными строками одинаковой длины. Операторы побитового сдвига сохраняют длины исходных строк, как показано в примерах.

Таблица 9.13. Операторы для работы с битовыми строками

Оператор Описание Пример Результат
|| конкатенация B'10001' || B'011' 10001011
& битовый AND B'10001' & B'01101' 00001
| битовый OR B'10001' | B'01101' 11101
# битовый XOR B'10001' # B'01101' 11100
~ битовый NOT ~ B'10001' 01110
<< битовый сдвиг влево B'10001' << 3 01000
>> битовый сдвиг вправо B'10001' >> 2 00100

Следующие функции языка SQL работают как с символьными, так и с битовыми строками: length, bit_length, octet_length, position, substring, overlay.

С битовыми и двоичными строками работают функции get_bit и set_bit. При работе с битовыми строками эти функции нумеруют биты слева направо и самый левый бит считается нулевым.

Кроме того, целые значения можно преобразовать в тип bit и обратно. Например:

44::bit(10)                    0000101100
44::bit(3)                     100
cast(-44 as bit(12))           111111010100
'1110'::bit(4)::integer        14

Заметьте, что приведение к типу «bit» без длины будет означать приведение к bit(1), и в результате будет получен только один менее значащий бит числа.

Примечание

Приведение целого числа к типу bit(n) копирует правые n бит числа. Если же целое преобразуется в битовую строку большей длины, чем требуется для этого числа, она дополняется слева битами знака числа.

Начинаем изучать STM32: битовые операции / Хабр

Небольшое отступление…

В прошлом уроке мы рассмотрели с чего начать, если вы решили изучать микроконтроллеры STM32: как настроить IDE, как создать простой проект, как откомпилировать программу и как запустить программу на выполнение. После полюбовались на перемигивание светодиодов на Discovery-плате )

Начиная новую статью, я задумывал сразу же перейти к подробному разбору листинга программы, который заставлял попеременно перемигиваться наши светодиоды, но уже перейдя к написанию, я вдруг осознал для себя, что есть большое количество вопросов без ответа на которые — перейти к рассмотрению программы было бы преждевременно. И для себя я определил целый перечень таких вопросов:

  1. Что же такое битовые операции? Как ими пользоваться?
  2. Что такое регистры и как они связаны с битовыми операциями?
  3. Из чего состоят микроконтроллеры STM32F0xx-серии, как осуществляется тактирование и чем обеспечена жизнь внутри МК?
  4. Как происходит стартовая инициализация МК, зачем нужен startup-файл, что делает функция SystemInit? Объяснение на пальцах.
  5. Из чего состоит библиотека CMSIS? Как в ней ориентироваться? Что полезного можно из нее извлечь и как ей пользоваться?

Именно с рассмотрения этих вопросов я хотел бы продолжить повествование о программировании STM32.

Список статей:

  1. Начинаем изучать STM32 или Управляем светом по-умному
  2. Начинаем изучать STM32: битовые операции
  3. Начинаем изучать STM32: Что такое регистры? Как с ними работать?

Основные логические операции

Только начиная изучать микроконтроллеры, слова «регистр» и «битовые операции» для меня казались чем-то таинственно загадочным и я долго не хотел переходить к рассмотрению данной темы. Но когда я более-менее разобрался с тем, что это такое я понял что зря откладывал изучение такой важной темы в дальний ящик. Битовые операции наверное самые распространенные операции в микроконтроллере и знание того, как и зачем их можно применять в нашей работе — откроет перед нами огромный потенциал возможностей для управления всем и вся в нашем МК!

Все мы на уроках информатики в школе знакомились с тем, что такое цифровая техника, почему она так называется, какие существуют основные логические операции.Все современные цифровые технологии основаны на двоичной математике и логических схемах.

Микроконтроллер всегда оперирует только двумя состояниями: «ноль» — нет напряжения, «единица» — есть напряжение. Давайте немного освежим в голове знания о основных логических операциях т.к. они составляют основу всей цифровой техники.

  • Конъюнкция — обозначается как «Логическое И» или «Логическое умножение». В сущности, результат от выполнения данной логической операции двух для выражений А и В подобен их умножению. То есть, выражение примет значение «1» в случае только если и А, и В имеют значение «1». Во всех других случаях будет значение «0». Может обозначаться как И, &&, AND, &
  • Дизъюнкция — обозначается как «Логическое ИЛИ» или «Логическое сложение». Результат от выполнения данной логической операции двух для выражений А и В подобен их сложению. То есть, выражение примет значение «1» в случае если хотя бы одно из выражений А и В имеют значение «1»..

Битовые операции

Битовые операции — практически то же самое, что и логические операции только лишь с той разницей, что применяются они к битам и к числам двоичной системы.

К слову говоря, для простоты изучения битовых операций я использовал программу 32-bit ASM Calculator от ManHunter. С помощью данной программы можно проверять результаты выполнения битовых операций, переводить числа из одной системы счисления в другую. Программа имеет интуитивно понятный интерфейс и после знакомства программа стала одним из основных инструментов в моей работе с микроконтроллерами. Небольшое пояснение к интерфейсу программы данно на изображении ниже:

Битовая операция «НЕ» — «~»

Если бит равен «1», то после выполнения операции «НЕ» он будет равен «0», и наоборот. Операция сразу же выполняется над всеми битами двоичного числа. Например, инвертируем число FF:

Битовая операция «И» — «&»

Если оба бита в разряде равны «1», то после выполнения операции «И» результат в разряде будет равен «1», но если хотя бы один из битов равен «0» тогда и результат будет равен «0». Операция так же выполняется поразрядно. Например, «умножим» два числа 0xFF0 и 0xF0F:

В результате мы увидим, что в тех разрядах где были единицы в обоих числах, в результате получились единицы, во всех остальных случаях — нули.

Рассмотрим варианты практического применения:

  • В ситуации, если нам необходимо сбросить конкретный бит или группу битов в ноль мы можем использовать маску. Думаю, будет нагляднее показать это на примере. Допустим, мы берем число и 0xF8F и нам надо чтобы 7-й бит стал вместо единицы нулем. Нет проблем, накидываем маску и снимаем галочку с нужного бита. Умножаем числа и получаем результат:
  • Если нам необходимо проверить конкретный бит в числе на 0 или 1 — мы так же используя маску проводим умножение. В маске мы устанавливаем бит, который хотели бы проверить. Если требуемый бит равен «0» — то результатом вычисления будет «0», если «1» то, соответственно, «1». Если мы хотим узнать, равен ли 7-й бит единицы — делаем соответствующую маску и умножаем наше число на маску. Все просто:


    Если нам нужно проверить четность числа(имеется ввиду способность числа делиться на два) — то мы таким же образом проверяем 1-й бит, если он равен «1» — то число нечетное, если там «0» то число четное. Попробуйте сами, в целях обучения и формирования навыков, сделать данную проверку.

Битовая операция «ИЛИ» — «|»

Если один или оба из пары битов равен «1» то результат будет «1», иначе если оба бита равны «0» то результат будет равен «0». То есть, грубо говоря, производится сложение всех единиц в разрядах. Например если мы складываем два числа 0xF8F и 0x7F, то получим следующий результат:

Рассмотрим вариант практического применения:

  • Если нам необходимо установит конкретный бит в числе на 1 — мы так же используя маску проводим сложение. Например, чтобы выставить 15-й бит в числе 0xFF0 нужно провести операцию логического сложения и мы получим требуемый результат:

Попробуйте самостоятельно поиграться с различными числами и понаблюдать за результатами.»

Если биты в разряде отличаются и не равны тогда результат будет «1», иначе «0». Например, если мы делаем XOR числа 0xF8F и 0x7F, то мы увидим что в разрядах в которых находятся отличные биты то там в результате получается «1» и в местах где биты одинаковые, будь то «0» или «1» — получился «0», в итоге мы получим следующий результат:

Рассмотрим варианты практического применения:

  • Если нам понадобилось инвертировать какие-либо биты в числе, можно используя маску с легкостью сделать это используя операцию XOR. Давайте сделаем инверсию 6 и 7 разряда в числе 0xF8 используя маску 0xC0. Результат вы можете посмотреть на изображении:
  • Бывают ситуации когда необходимо сравнить два регистра и определить равны они или нет. В этом случае нам необходимо значения регистров подвергнуть операции XOR. Если результат получился «0» — тогда регистры равны, иначе — не равны:

Битовые операции сдвига

Существует ряд интересных и порой чрезвычайно полезных битовых операций именуемых как операции сдвига. Двигать разряды можно как вправо, так и влево. В ходе данной операции происходит сдвиг всех разрядов двоичного числа на указанное количество позиций, при этом, в случае если сдвиг идёт влево — старший бит (самый левый) теряется, а в младший (самый правый) записывается «0». При логическом сдвиге вправо происходит обратная ситуация — младший бит (самый правый) теряется, а в старший записывается «0». Дополнительно хотелось бы отметить, что в случае 32-разрядных слов сдвигаются все 32 разряда целиком. Рассмотрим операции сдвига подробнее.

Cдвиг влево — «<<«

То, как происходит сдвиг вы можете увидеть на изображении ниже. Думаю, что всё достаточно очевидно:

При двоичном сдвиге можно заметить одну интересную особенность. Сдвиг на один разряд умножает наше число на 2. Если сдвинуть на n разрядов наше число x то получится x * (2 * n). Попробуйте самостоятельно отследить эту закономерность через нашу утилку для подсчета. =)

Cдвиг вправо — «>>»

То, что получается в результате сдвига вправо достаточно наглядно отражено на изображении:

При двоичном сдвиге вправо можно заметить что происходит ситуация обратная сдвигу влево — число делится на 2 с при сдвиге в 1 разряд и после на 2 * n, где n — количество разрядов на которые произведен сдвиг. Так же попробуйте самостоятельно поиграться с числами и которые заведомо делятся на 2 нацело. И вопрос на засыпку — какой результат будет если вы поделите таким образом нечетное число?

Важное замечание. Если вы будете делать сдвиг для переменной с отрицательным знаком (signed) — освободившиеся позиции будут заполняться единичками.

В качестве заключения…

Многим начинающим данная тема может показаться дико скучной и может сложиться ощущение что ну вообще не понятно где и как можно применить эти знания. Спешу вас обнадежить, в ситуациях когда нужно поднять ту или иную ногу МК или записать параметр в какой-нить периферийный блок или модуль — там кругом и всюду будут требоваться знания битовых операций. Так как статья получилась достаточно объемной, рассмотрение регистров мы перенесем на следующий урок. Ну и в последующем можно использовать эту статью как шпаргалку.

В качестве домашнего задания попробуйте самостоятельно разобрать код нашей программы в блоке while(1) {… } и понять как же битовыми операциями мы включаем и выключаем наши светодиоды. Ну а на следующем уроке я расскажу как оно происходит на самом деле!

Список статей:

  1. Начинаем изучать STM32 или Управляем светом по-умному
  2. Начинаем изучать STM32: битовые операции
  3. Начинаем изучать STM32: Что такое регистры? Как с ними работать?

Битовые операции

Во многих языках программирования допустимы логические операции над битами целых чисел. В отличие от обычных логических операций, результатом выполнения которых является логический тип данных, битовые логические операции просто изменяют целое число согласно определенным правилам. Точнее битовые операции изменяют отдельные биты двоичного представления числа, в результате чего изменяется его десятичное значение.

Например, в языке программирования Паскаль обычные логические операции и логические операции над битами обозначают с помощью одних и тех же ключевых слов: not, and, or, xor. Компилятор определяет, что имелось в виду в зависимости от контекста использования этих слов. Обычные логические операции объединяют два и более простых логических выражения. Например, (a > 0) and (c != b), (c < a) or(not b) и т.п. В свою очередь побитовые логические операции выполняются исключительно над целыми числами (или переменными, которые их содержат). Например, a and b, a or 8, not 247.

Как понять побитовые операции

1. Переведем пару произвольных целых чисел до 256 (один байт) в двоичное представление.

     6710 = 0100 00112
    11410 =  0111 00102

2. Теперь расположим биты второго числа под соответствующими битами первого и выполним обычные логические операции к цифрам, стоящим в одинаковых разрядах первого и второго числа. Например, если в последнем (младшем) разряде одного числа стоит 1, а другого числа — 0, то логическая операция and вернет 0, а or вернет 1. Операцию not применим только к первому числу.

3. Переведем результат в десятичную систему счисления.

    01000010 = 26 + 21 = 64 + 2 = 66
    01110011 = 26 + 25 + 24 + 21 + 20 = 64 + 32 + 16 + 2 + 1 = 115
    00110001 = 25 + 24 + 20 = 32 + 16 + 1 = 49
    10111100 = 27 + 25 + 24 + 23 + 22 = 128 + 32 + 16 + 8 + 4 = 188
    

4. Итак, в результате побитовых логических операций получилось следующее:

    67 and 114 = 66
    67 or 114 = 115
    67 xor 114 = 49
    not 67 = 188
    

Вот еще один пример выполнения логических операций над битами. Проверьте его правильность самостоятельно.

    5 and 6 = 4
    5 or 6 = 7
    5 xor 6 = 3
    not 5 = 250
    

Зачем нужны побитовые логические операции

Глядя на результат побитовых операций, не сразу можно уловить закономерности в их результате. Поэтому непонятно, зачем нужны такие операции. Однако, они находят свое применение. В байтах не всегда хранятся числа. Байт или ячейка памяти может хранить набор флагов (установлен — сброшен), представляющих собой информацию о состоянии чего-либо. С помощью битовых логических операций можно проверить, какие биты в байте установлены в единицу, можно обнулить биты или, наоборот, установить в единицу. Также существует возможность сменить значения битов на противоположные.

Проверка битов

Проверка битов осуществляется с помощью битовой логической операции and.

Представим, что имеется байт памяти с неизвестным нам содержимым. Известно, что логическая операция and возвращает 1, если только оба операнда содержат 1. Если к неизвестному числу применить побитовое логическое умножение (операцию and) на число 255 (что в двоичном представлении 1111 1111), то в результате мы получим неизвестное число. Обнулятся те единицы двоичного представления числа 255, которые будут умножены на разряды неизвестного числа, содержащие 0. Например, пусть неизвестное число 38 (0010 0110), тогда проверка битов будет выглядеть так:

Другими словами, x and 255 = x.

Обнуление битов

Чтобы обнулить какой-либо бит числа, нужно его логически умножить на 0.

Обратим внимание на следующее:

    1111 1110 = 254 = 255 - 1 = 255 - 20
    1111 1101 = 253 = 255 - 2 = 255 - 21
    1111 1011 = 251 = 255 - 4 = 255 - 22
    1111 0111 = 247 = 255 - 8 = 255 - 23
    1110 1111 = 239 = 255 - 16 = 255 - 24
    1101 1111 = 223 = 255 - 32 = 255 - 25
    1011 1111 = 191 = 255 - 64 = 255 - 26
    0111 1111 = 127 = 255 - 128 = 255 - 27

Т.е. чтобы обнулить четвертый с конца бит числа x, надо его логически умножить на 247 или на (255 — 23).

Установка битов в единицу

Для установки битов в единицу используется побитовая логическая операция or. Если мы логически сложим двоичное представление числа x с 0000 0000, то получим само число х. Но вот если мы в каком-нибудь бите второго слагаемого напишем единицу, то в результате в этом бите будет стоять единица.

Отметим также, что:

    0000 0001 = 20 = 1
    0000 0010 = 21 = 2
    0000 0100 = 22 = 4
    0000 1000 = 23 = 8
    0001 0000 = 24 = 16
    0010 0000 = 25 = 32
    0100 0000 = 26 = 64
    1000 0000 = 27 = 128
    

Поэтому, например, чтобы установить второй по старшинству бит числа x в единицу, надо его логически сложить с 64 (x or 64).

Смена значений битов

Для смены значений битов на противоположные используется битовая операция xor. Чтобы инвертировать определенный бит числа x, в такой же по разряду бит второго числа записывают единицу. Если же требуется инвертировать все биты числа x, то используют побитовую операцию исключающего ИЛИ (xor) с числом 255 (1111 1111).

Операции побитового циклического сдвига

Помимо побитовых логических операций во многих языках программирования предусмотрены битовые операции циклического сдвига влево или вправо. Например, в языке программирования Паскаль эти операции обозначаются shl(сдвиг влево) и shr (сдвиг вправо).

Первым операндом операций сдвига служит целое число, над которым выполняется операция. Во втором операнде указывается, на сколько позиций сдвигаются биты первого числа влево или вправо. Например, 105 shl 3 или 105 shr 4. Число 105 в двоичном представлении имеет вид 0110 1001.

При сдвиге влево теряются старшие биты исходного числа, на их место становятся младшие. Освободившиеся младшие разряды заполняются нулями.

При сдвиге вправо теряются младшие биты исходного числа, на их место становятся старшие. Освободившиеся старшие разряды заполняются нулями, если исходное число было положительным. $y Задаются биты, которые установлены в $x или в $y, но не в обоих сразу. ~ Not ~$x Биты, которые установлены в $x, не задаются. И наоборот. << Shift left $x << $y Смещение битов $x на $y шагов влево.# >> Shift right $x >> $y Смещение битов $x на $y шагов вправо.*

# Каждый шаг означает «умножить на два».
* Каждый шаг означает «поделить на два».

В PHP битовых операциях бит (двоичный разряд) — это базовая единица информации, хранящаяся в вычислительной системе, которая существует в двух возможных состояниях, представленных как ON или OFF. В компьютерной системе состояние ON рассматривается как состояние 1, а OFF — 0. Эти состояния можно сравнить с двумя состояниями электрического переключателя (ВКЛ и ВЫКЛ) и т.п.

Значения задаются в двоичной системе исчисления. В десятичной системе построение числа основано на 10. Давайте посмотрим, как можно построить десятичное число —

231 = (2 ; 102 ) + (3 ; 101) + (1 ; 100)
= 200 + 30 + 1
= 231

Система двоичных чисел следует той же концепции. Единственное отличие состоит в том, что база равна 2, а не 10. Посмотрим, как двоичное число преобразовать в десятичное —

1011010=(1 x 26)+(0 x 25)+(1 x 24)+(1 x 23)+(0 x 22)+(1 x 21)+(0 x 20)
=(1 x 64) +(0 x 32)+(1 x 16)+(1 x 8)+(0 x 4)+(1 x 2)+(0 x 1)
=64+0+16+8+0+2+0
=90

Таким образом, (1011010)2= (90)10

Байт состоит из последовательности битов. Байт состоит из восьми битов. Максимальное значение байта составляет 255. Таким образом устанавливается значение места каждого бита.

1 байт (8 бит)
8-ой 7-ой 6-ой 5-ый 4-ый 3-ий 2-ой 1-ый
Установленное значение 128 64 32 16 8 4 2 1

Табличное представление байта, демонстрирующее, как максимальное значение байта в битовой операции с числами составляет 255:

1 байт (8 бит)
Установленное значение 128 64 32 16 8 4 2 1
1 1 1 1 1 1 1 1
27 26 25 24 23 22 21 20
128 64 32 16 8 4 2 1 = 255

Десятичное число 93 может быть представлено в двоичной форме:

1 байт (8 бит)
Установленное значение 128 64 32 16 8 4 2 1
0 1 0 1 1 1 0 1
27 26 25 24 23 22 21 20
0 64 0 16 8 4 0 1 = 93
<?php  
$x=13;  
$y=22;  
echo $x & $y;  
?>

Результат примера:

4

Пояснение

Опираясь на приведенные выше таблицы битовых операций, можно сказать, что единственный общий бит находится на третьей позиции с установленным значением 4. Таким образом, $x & $y = 4:

1 байт (8 бит)
Установленное значение 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 1 0 1 = 13
$y 0 0 0 1 0 1 1 0 = 22

В приведенной выше таблице для $x (13) установлено значение на первой, третьей и четвертой позиции. Значения позиции равны 1, 4 и 8. А для $y (22) значение установлено на второй, третьей и пятой позициях с соответствующими значениями: 2, 4 и 16.

Единственный бит, который является общим для $x и $y — это третий. Поэтому возвращается 4.

Рассмотрим другой пример оператора &, в котором больше бит.

<?php  
$x=77;  
$y=198;  
echo $x & $y;  
?>

Результат примера PHP битовой операции:

68

Пояснение

1 байт (8 бит)
Place Value 128 64 32 16 8 4 2 1
$x 0 1 0 0 1 1 0 1 = 77
$y 1 1 0 0 0 1 1 0 = 198

В приведенной выше таблице значение установлено для $x (77) на первой, третьей, четвертой и седьмой позиции. Значения позиций равны 1, 4, 8 и 64. Значение для $y (198) установлено на второй, третьей, седьмой и восьмой позициях с соответствующими значениями: 2, 4, 64 и 128.

Из приведенной выше таблицы видно, что общие для $x и $y — это третий и седьмой биты. Таким образом, возвращается 64 + 4 = 68.

Пример побитового оператора PHP OR:

<?php  
$x=5;  
$y=11;  
echo $x | $y;  
?>

Результат примера:

15

Пояснение

1 байт (8 бит)
Place Value 128 64 32 16 8 4 2 1
$x 0 0 0 0 0 1 0 1 = 5
$y 0 0 0 0 1 0 1 1 = 11

В приведенной выше таблице битовой операции с числами для $x (5) значение установлено на первой и третьей позициях. Значения позиций соответственно равны 1 и 4. Для $y (11) значения установлены на первой, второй и четвертой позициях с соответствующим значением: 1, 2 и 8.

Для $x и $y общими являются первый, второй, третий или четвертый бит. Возвращаемое значение представляет собой добавление значений позиций битов, то есть: 8 + 4 + 2 + 1 = 15.

Оператор Xor также выполняет побитовое сравнение двух числовых выражений и в результате устанавливает общий бит. Когда одно и только одно выражение является истинным, тогда он возвращает true.

В приведенной ниже таблице показано, как выполняется операция XOR:

Выражение 1 Выражение 2 Результат
False False False
False True True
True False True
True True False

В приведенной ниже таблице показано побитовое сравнение оператора XOR:

Бит в Выражении 1 Бит в Выражении 2 Результат
0 0 0
0 1 1
1 0 1
1
<?php  
$x=12;  
$y=11;  
echo $x ^ $y;  
?>

Результат примера PHP битовой операции:

7

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 1 0 0 = 12
$y 0 0 0 0 1 0 1 1 = 11

В приведенной выше таблице для $x (12) установлено значение на третьей и четвертой позиции. Значения позиции равны 4 и 8. А для $y (11) значение задано на первой, второй и четвертой позиции с соответствующими значениями: 1, 2 и 8.

$ x и $ y устанавливают вместе первый, второй, третий или четвертый бит. Но вместе они используют только 4-й бит. Поэтому возвращаемое значение представляет собой добавление места для них. Но не совместно используемый бит: 4 + 2 + 1 = 7.

В приведенной ниже таблице будет показано, как оператор NOT выполняет операции с $x и $y и возвращает true, когда заданный в одном выражении бит не задан в другом выражении.

<?php  
$x=12;  
$y=10;  
echo $x & ~ $y;  
?>

Результат примера:

4

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 1 0 0 = 12
$y 0 0 0 0 1 0 1 0 = 10

В приведенной выше таблице для $x (12) значение установлено на третьей и четвертой позициях. Значения позиции равны 4 и 8. Для $y (10) значение установлено на второй и четвертой позициях с соответствующими значениями 2 и 8.

Для $x и $y заданы биты на первой, второй, третьей и четвертой позициям, но общий для них только четвертый бит. Возвращаемое значение равно 4, так как это единственное значение, заданное для $x, но не заданное для $y.

<?php  
$x=12;  
$y=10;  
echo ~ $x &  $y;  
?>

Результат примера:

2

Пояснение

В этом случае возвращаемое значение равно 2, потому что бит установлен для $y, но не установлен для $x.

Если a и b — это два числа. Оператор сдвига перемещает бит b на определенное количество позиций. Каждый шаг означает умножение на два (сдвиг влево). Если это сдвиг вправо, тогда каждый шаг означает деление на два.

<?php  
$x=8;  
$y=3;  
echo $x << $y;  
?>

Результат примера битовой операции

64

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 0 0 0 = 8
Результат 0 1 0 0 0 0 0 0 = 64

В приведенном выше примере берется значение $x, которое равно 8, и выполняется операция сдвига влево: 8 умножается на 2 три раза. Таким образом, мы получаем 8 x 2 x 2 x 2 = 64.

<?php  
$x=12;  
$y=4;  
echo  $x << $y;  
?>

Результат примера:

192

Пояснение

В приведенном выше примере берется значение $x, равное 12, и выполняется операция сдвига влево: умножается на 2 четыре раза. Таким образом, мы получаем 12 x 2 x 2 x 2 x 2 = 192.

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 1 0 0 = 12
Результат 1 1 0 0 0 0 0 0 = 192
<?php  
$x=8;  
$y=3;  
echo $x >> $y;  
?>

Результат примера битовой операции с числами:

1

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 0 0 0 1 0 0 0 = 8
Результат 0 0 0 0 0 0 0 1 = 1

В приведенном выше примере берется значение $x, которое равно 8, и выполняется операция сдвига вправо: 8 делится на 2 три раза. Таким образом, получаем 8/2 = 4/2 = 2/2 = 1.

<?php  
$x=96;  
$y=5;  
echo  $x >> $y;  
?>

Результат примера:

3

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 1 1 0 0 0 0 0 = 96
Результат 0 0 0 0 0 0 1 1 = 3

В приведенном выше примере берется значение $x, равное 96, и выполняется операция сдвига вправо: 96 делится на 2 пять раз. Таким образом, мы получаем 96/2 = 48/2 = 24/2 = 12/2 = 6/2 = 3.

<?php  
$x=64;  
$y=7;  
echo  $x >> $y;   
?>

Результат примера PHP битовой операции:

0

Пояснение

1 байт (8 бит)
Значение позиции 128 64 32 16 8 4 2 1
$x 0 1 0 0 0 0 0 0 = 64
Результат 0 0 0 0 0 0 0 0 = 0

В приведенном выше примере берется значение $x, равное 64, и выполняется операция сдвига вправо: 64 делится на 2 семь раз. При делении в определенный момент нам нечего делить. Таким образом, результат равен 0.

Данная публикация является переводом статьи «PHP Bitwise operator» , подготовленная редакцией проекта.

Swift: Операции с битами | Каморка сурового программиста

– оператор NOT (логическое НЕ, инвертирует все биты, ~)

00001111
~
11110000

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 11110000



00001111

~

11110000

 

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits  // 11110000

UInt8 – целые беззнаковые числа в диапазоне от 0 до 255

– оператор AND (логическое И, выдаст 1, если оба бита = 1, &)

11111100
&
00111100
———
00111100


let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 00111100



11111100

&

00111100

———

00111100

 

 

let firstSixBits: UInt8 = 0b11111100

let lastSixBits: UInt8  = 0b00111111

let middleFourBits = firstSixBits & lastSixBits  // 00111100

– оператор OR (логическое ИЛИ, выдаст 1 если хоть один бит = 1, ||)

10110010
||
11111110
———
11111110

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 11111110



10110010

||

11111110

———

11111110

 

let someBits: UInt8 = 0b10110010

let moreBits: UInt8 = 0b01011110

let combinedbits = someBits | moreBits  // 11111110

– оператор XOR (логическоле ИЛИ-НЕ, выдаст 1 если биты разные, 0 и 1, иначе 0, ^)

00010100
^
00010001
———
00000101

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 00010001



00010100

^

00010001

———

00000101

 

let firstBits: UInt8 = 0b00010100

let otherBits: UInt8 = 0b00000101

let outputBits = firstBits ^ otherBits  // 00010001

– операторы побитового сдвига целых чисел влево и вправо >>
По сути равно умножению на два при сдвиге влево, и делению на 2 (с отбрасыванием единицы если число было нечетным) при сдвиге вправо

Для беззнаковых все довольно просто.

Сдвиг влево

11111111 << 1 =
1(11111110)



  11111111 << 1 =

1(11111110)

За скобки я вынес единицу которая грубо говоря пропадает. Чтобы была понятна природа сдвига – берем ноль справа (считаем что и слева и справа от числа идет нулевой бит) дописываем его справа и отбрасываем последний бит слева. Равноценно умножению на 2.

Сдвиг вправо

11111111 >> 1 =
(01111111)1



11111111 >> 1 =

(01111111)1

При сдвиге вправо ноль добавляется уже слева и отбрасываем последний правый бит. Как я уже упоминал равноценно делением на два нацело.

let shiftBits: UInt8 = 4 // 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001



let shiftBits: UInt8 = 4   // 00000100

shiftBits << 1             // 00001000

shiftBits << 2             // 00010000

shiftBits << 5             // 10000000

shiftBits << 6             // 00000000

shiftBits >> 2             // 00000001

Сдвиги можно применять чтобы выделить часть составного значения. К примеру цвет можно закодировать как 3х-компонентное число (пресловутое RGB), чтобы из такого числа получить конкретные части красного, зеленого и синего цветов соответственно пригодится система сдвигов.

let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 0xCC, или 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 0x66, или 102
let blueComponent = pink & 0x0000FF // blueComponent 0x99, или 153



let pink: UInt32 = 0xCC6699

let redComponent = (pink & 0xFF0000) >> 16    // redComponent 0xCC, или 204

let greenComponent = (pink & 0x00FF00) >> 8   // greenComponent 0x66, или 102

let blueComponent = pink & 0x0000FF           // blueComponent 0x99, или 153

К примеру разберем let greenComponent = (pink & 0x00FF00) >> 8
Сначала мы выполняем побитовое сложение над числом и маской 0x00FF00, выделяем таким образом именно зеленый цвет, но нам нужно получить число от 0 до 255 (0x00 – 0xFF), а мы получаем (0x00000xFF00).n = 128 = 10000000

Итого

10000000
-
00000100
——-
01111100
И выставляя бит со знаковой единицей слева получаем
11111100



10000000

00000100

——-

01111100

И выставляя бит со знаковой единицей слева получаем

11111100

Данное представление используется потому, что арифметические действия над такими числами производятся обычным путем. К примеру произведем сложение -1 и -4

11111100 (-4)
+
11111111 (-1)
————
111111011 (-5)



11111100 (-4)

+

11111111 (-1)

————

111111011 (-5)

Производить сдвиги для знаковых числе благодаря такому представлению не сложнее чем для беззнаковых, нужно лишь при сдвиге вправо в качестве нового старшего бита брать не всегда 0, а текущий знаковый бит

11111111 >> 1 =
11111111

01111111 >> 1 =
00111111



11111111 >> 1 =

11111111

 

01111111 >> 1 =

00111111

Работа с битами в JavaScript. Исследование побитовых операций и… | by Dan Romans

Постановка задачи:

Для данного n , 32-битного целого числа без знака, переверните биты его двоичного представления ( 0 -> 1 | 1 -> 0 ) и распечатайте результат как целое число без знака. . Например:

 n = 12345600000000000000011110001001000000  = 123456  ₁₀  
111111111111100001110110111111 = 4294843839 ₁₀ результат = 4294843839

в десятичном виде ) или двоичный (базовый ).Десять цифр, как в десятичной системе, или две, как в двоичной системе: 0 и 1 .

* Целое число без знака — это 32-битная система данных, которая кодирует неотрицательное целое число в диапазоне от 0 до 4294967295 . Для решения этой задачи просто учтите, что мы будем работать с 32-битными целыми числами и только с положительными значениями, а не с отрицательными значениями.

Описание функции:

Функция flippingBits () должна возвращать десятичное целое число без знака.

Входной параметр:

Ограничения:

Программное решение:

Логика довольно проста: преобразовать десятичное целое число в двоичное, инвертировать его нули и единицы, преобразовать инверсию в десятичное целое. Давайте рассмотрим алгоритм, а затем объясним каждый процесс.

  function flippingBits (n) {
// объявление переменных для двоичного, обратного двоичного и десятичного результата
let lowBin = ''
let highBin = ''
let result = 0
// преобразование ввода десятичное в двоичное
while (n> = 1) {
const rem = n% 2
lowBin + = rem
rem === 1?
n = Математика.floor (n / 2):
n / = 2
}
// настроить двоичный код на 32 бита
while (lowBin.length <32) {
lowBin + = 0
}
// инвертировать и инвертировать каждый двоичный бит
для (let i = lowBin.length - 1; i> = 0; i--) {
highBin + = lowBin [i] === '0'? '1': '0'
}
// преобразовать двоичный в десятичный вывод
for (let i = 0; i const expo = highBin.length - 1 - i
result + = highBin [i] * (2 ** expo)
}
return result
}
  1. Объявление переменных для двоичного, обратного двоичного и десятичного результата:
  let lowBin = '' 
let highBin = ''
let result = 0

Двоичное целое число представлено слева направо, как старшего бит до младшего бит.Переменная lowBin будет представлять двоичный код в его первом состоянии, от низкого к высокому, что будет из-за того, как мы алгебраически преобразуем ввод. Переменной highBin будет назначена обратная копия lowBin , поэтому у нас будет правильная структура высокого порядка младшего разряда бит. lowBin и highBin имеют тип string , с которыми будет удобно работать. Переменной result будет присвоено десятичное преобразование значения, содержащегося в highBin , и которое в конечном итоге будет использоваться в качестве возвращаемого значения. Результат относится к типу number, созданному по адресу 0 , поскольку он будет вычисляться аддитивно.

2. Преобразуйте входное десятичное число в двоичное:

Сначала давайте обсудим, как выполнить эту процедуру алгебраически. Десятичное целое число можно преобразовать в двоичное, непрерывно равномерно разделив целое число на 2 (как в базе , как в двоичном), пока значение не достигнет 0 , сохраняя результирующие остатки в последовательности как биты.При равном делении любого числа на 2 остаток должен быть 0 или 1 . Рассмотрим следующий пример:

 Разделите 21 на 2, пока результат не станет равен нулю. Остатки этих делений будут битами, используемыми для создания двоичного файла: 21/2 = 10 (+) rem 1  бит младшего разряда  
10/2 = 5 (+) rem 0
5/2 = 2 (+) rem 1
2/2 = 1 (+) rem 0
1/2 = 0 (+) rem 1 h бит старшего разряда Организуйте остаток битов в двоичное целое число: 10101

1/2 = 0 (+) rem 1 немного сбивает с толку, но учтите, что мы работаем с целыми числами, и, как только значение уменьшится до 1 , оно не может быть равномерно разделено как целое число, оставаясь больше 0 , поэтому само значение — 1 — становится остатком.

  // выполняем последнюю операцию, когда значение 1 передается в   
while (n> = 1) {
// оператор остатка (оператор по модулю) извлекает
// остаток от n / 2 и присваивает значение, бит, rem

const rem = n% 2
// rem добавляется к строке lowBin
// обратите внимание на важность сохранения двоичной последовательности
// как строки, что было бы сложнее численно
// rem, число, приводится к строке и может быть объединено
lowBin + = rem // условный оператор используется для корректировки значения n
// для следующая итерация
// если rem равно 1, n - нечетное значение, поэтому n присваивается
// значение n / 2, округленное в меньшую сторону с помощью Math.floor ()
// в противном случае rem должно быть равно 0, что указывает на четное число,
// и n присваивается значение n / 2
// это всегда гарантирует, что в цикл будет передано целое число
rem === 1?
n = Math.floor (n / 2):
n / = 2
}

3. Установить двоичный код на 32 бита:

  while (lowBin.length <32) {
lowBin + = 0
}

Предыдущий цикл while, который создает двоичную строку, создает ровно столько битов, сколько необходимо для преобразования десятичного ввода.Остальным процессам требуется 32-битное значение, поэтому мы просто используем цикл while, установленный с ограничением 32 , для добавления нулей к существующей двоичной строке. lowBin + = 0 работает, потому что 0 преобразуется в строку , но вы также можете быть прямым и использовать lowBin + = «0» .

Обратите внимание, что из-за линейного порядка работы предыдущего цикла при выполнении цикла двоичная строка в этой точке находится в обратном порядке - от младшего порядка до высокого порядка - поэтому двоичное значение может быть дополнено нулями из справа, добавляя их вместо обычной процедуры заполнения двоичного значения нулями слева, добавляя их.

4. Инвертировать и инвертировать каждый бит двоичного файла:

  для (let i = lowBin.length - 1; i> = 0; i--) {
highBin + = lowBin [i] === ' 0 '? '1': '0'
}

Мы используем убывающий для цикла , чтобы перебирать двоичную строку, содержащуюся в lowBin - конец в начало - i = 31 -> i = 0 . На каждой итерации к highBin добавляется значение, обратное текущему значению ( lowBin [i] ), которое определяется условным оператором: если 0 , вернуть 1 ; если 1 , вернуть 0 .Этот процесс эффективно переворачивает двоичную строку при инвертировании каждого бита.

5. Преобразуйте двоичный формат в десятичный:

И снова давайте обсудим, как выполнить эту процедуру алгебраически. Двоичное целое число можно преобразовать в десятичное целое, умножив каждую цифру на собственную степень 2 и суммируя результаты, , т.е. каждый бит, 0 или 1 , можно умножить на 2 , чтобы получить затем суммировали мощность n . 2 коррелирует с основанием , а n коррелирует с положением бита, как индекс. Например:

 Преобразует 1001₂ в десятичное целое число. 
__________________________________
| бит | 1 | 0 | 0 | 1 |
| ———————————— | ———— | ———— | ———— | ———— |
| мощность 2 | 2³ | 2² | 2¹ | 2⁰ |
———————————————————————————————————
Обратите внимание, что степени 2 отражают нулевой индекс, а - по убыванию. , потому что
двоичных целых чисел упорядочены от высокого к низкому порядку.1001₂ = (1 * 2³) + (0 * 2²) + (0 * 2¹) + (1 * 2⁰) = 9₁₀ Это можно далее разрезать следующим образом: 1 * (2 * 2 * 2) = 8
+ 0 * ( 2 * 2) = 0
+ 0 * (2) = 0
+ 1 * (1) = 1 ( n⁰ всегда равно 1 )
—––––––––––––––– ––––––––
9

Используя цикл for , мы получаем удобный доступ к значению и положению каждого бита или индексу.

  // вместо установки условия ограничения на highBin.length - 1 
// мы устанавливаем на highBin.length (32), чтобы мы могли разместить
// уменьшение битового индекса до 0 для переменной expo
// highBin.length - 1 завершит формулу на n * 2¹
// если последний вычисленный бит был 0 (0 * 2⁰ = 0), последнее добавление
// можно игнорировать, однако это условие необходимо, потому что
// последний вычисленный бит может быть 1 (1 * 2⁰ = 1), что влияет на результат
// с последнее добавление 1
для (let i = 0; i // expo присваивается номер, который представляет позицию каждого бита
// на первой итерации это будет: const expo = 32 - 1 - 0 = 31
const expo = highBin.length - 1 - i // если первый бит был 0 (highBin [i] = 0), то первое
// добавление формулы преобразования было бы: 0 * 2³¹
// на каждой итерации добавляемое значение суммируется в результат
результат + = highBin [i] * (2 ** expo)
}

6. Возвращаемый результат:

И последнее, но не менее важное: результат будет возвращен функцией .

 const n = 123456 flippingBits (n) // lowBin = 00000010010001111000000000000000 
// перевернуть биты и перевернуть строку
// highBin = 111111111111100001110110111111 // => 4294843839

Оптимизированное программное решение:

term optimized. Я использую термин

.Я имею в виду, что это сжато, но я не тестировал различия в производительности этих трех решений, поэтому пока не могу сообщить об истинной оптимизации. Однако моя поверхностная оценка такова, что да, это решение оптимизировано, и предстоящее сверхкомпактное побитовое решение кажется довольно быстрым!

Предыдущее решение похоже на большой объем кода, чтобы перевернуть некоторые биты, не так ли? Так уж получилось, что JavaScript и, предположительно, большинство других языков имеют некоторые встроенные методы и функции, которые могут уменьшить наш алгоритм.

  function flippingBits (n) { 
// метод Number.toString () удобно преобразовывает число
// в строку И он может принимать аргумент radix для преобразования
// числа в строку определенной базы, например base ₂ для двоичного кода
let lowBin = n.toString (2)
let highBin = ''
// Number.toString () создает двоичное строковое представление в
// исправьте последовательность старшего порядка на младшую, поэтому нули
// дополняются слева, добавляя их в начале

while (lowBin.length <32) {
lowBin = 0 + lowBin
}
// поскольку последовательность от высокого порядка к младшему уже верна
// этому циклу for нужно только перевернуть биты, а не перевернуть строку

for (let i = 0; i highBin + = lowBin [i] === '0'? '1': '0'
}
// функция parseInt () удобно преобразует строку в число
// И может принять второй аргумент системы счисления в
// преобразовать строку в номер конкретной базы , например base ₂
return parseInt (highBin, 2)
}

Комментарии в сторону, это более короткая и более чистая функция, чем предыдущая.Он использует мощь встроенных модулей, требует меньше памяти и выполняет меньше итераций. Возможно ли, чтобы этот алгоритм можно было объединить в одну производительную строку кода и добиться того же результата? Барабанная дробь, пожалуйста.

Спасибо, джазовый мессенджер!

Побитовое решение:

  const flippingBits = n => ~ n >>> 0  

Вы можете в это поверить? Я почти не знал! Функция жирной стрелки ES6 немного помогает (без каламбура), но оператор функции / возвращаемое значение настолько лаконичны, что вызывают восхищение.Функция получает аргумент, число n , и возвращает десятичное целочисленное представление обратной двоичной последовательности n . Но как?

  1. Оператор побитового НЕ:

Оператор Побитовое НЕ обозначается как ~ и инвертирует двоичное значение каждого бита операнда. Это означает, что под капотом двоичное представление числа инвертировано. Каждые 0 переворачиваются в 1 , каждые 1 переворачиваются в 0 . 010100 станет 101011 . Однако есть один нюанс.

Полученное значение представляет собой целое число со знаком. высокого порядка бит, , т.е. крайний левый бит , целого числа со знаком также известен как знаковый бит и указывает знак числа, , то есть , является ли число положительным ( 0 ) или отрицательным ( 1 ). Для наших целей мы понимаем, что знаковый бит инвертируется вместе со всеми другими битами, преобразуя положительный входной сигнал в отрицательный выход.

Наша задача кода требует, чтобы мы вернули положительное 32-разрядное целое число без знака. У нас ничего этого нет после использования оператора Bitwise NOT . Не бойтесь, есть еще один побитовый оператор, который спасет положение.

2. Оператор сдвига вправо с заполнением нулем:

Оператор с заполнением нуля вправо обозначается как >>> и принимает аргумент, определяющий количество сдвигов. Он сдвигает старший бит на указанное количество бит вправо.Лишние биты отбрасываются вправо, а нулевые ( 0 ) биты добавляются слева. Поскольку знаковый бит становится нулевым, значение становится положительным.

Для этого решения заданное количество сдвигов составляет 0 , что по существу создает ситуацию, когда наша двоичная последовательность дополняется нулями до тех пор, пока она не станет 32-битной последовательностью, а оператор с заполнением нулями вправо сдвиг всегда возвращает 32-битное целое число без знака . Беги домой.

Краткое руководство по побитовым операторам в Java

Оператор сдвига влево и вправо

Битовый сдвиг - это побитовая операция, при которой порядок последовательности битов перемещается для эффективного выполнения математической операции.Битовый сдвиг перемещает каждую цифру в двоичном представлении числа влево или вправо на количество пробелов, заданное вторым операндом.

Эти операторы могут применяться к целым типам, таким как int , long , short , byte или char .

Есть три вида смен:

  • Сдвиг влево: << - оператор сдвига влево, отвечающий требованиям как логических, так и арифметических сдвигов.
  • Арифметический / знаковый сдвиг вправо: >> - это арифметический (или знаковый) оператор сдвига вправо.
  • Логический / беззнаковый сдвиг вправо: >>> - логический (или беззнаковый) оператор сдвига вправо.

В Java все целочисленные типы данных подписаны, а << и >> представляют собой исключительно арифметические сдвиги.

Вот пример сдвига влево:

6 = 000000006 = 000000006 = 00000000 000000000000000000000000 000000000000000000000000 000001100000011000000110

Сдвиг этой битовой комбинации на одну позицию влево ( 6 << 1 ) приводит к числу 12:

6 << 1 = 000000006 << 1 = 000000006 << 1 = 00000000 000000000000000000000000 000000000000000000000000 000011000000110000001100

Как видите, цифры смещены влево на одну позицию, а последняя цифра справа заполнена нулем.36 * 2 3 → 6 * 86 * 86 * 8

Хорошо оптимизированные компиляторы будут использовать это правило для замены умножения сдвигами, когда это возможно, поскольку сдвиги выполняются быстрее.

Как работают биты и байты

Если вы использовали компьютер более пяти минут, то вы слышали слова бит и байт . Объем оперативной памяти и жесткого диска измеряется в байтах, как и размеры файлов, когда вы просматриваете их в программе просмотра файлов.

Вы можете услышать рекламу, которая гласит: "Этот компьютер оснащен 32-разрядным процессором Pentium с 64 мегабайтами RAM и 2.1 гигабайт места на жестком диске ". И многие статьи HowStuffWorks говорят о байтах (например, как работают компакт-диски). В этой статье мы обсудим биты и байты, чтобы вы имели полное представление о них.

Десятичные числа

Самый простой способ понять биты - сравнить их с тем, что вы знаете: цифр, . Цифра - это единое место, которое может содержать числовые значения от 0 до 9. 0) = 6000 + 300 + 50 + 7 = 6357

Что можно увидеть из этого выражения состоит в том, что каждая цифра является заполнителем для следующей более высокой степени 10, начиная с первой цифры с 10, возведенной в степень нуля.

Это все должно быть удобно - мы работаем с десятичными числами каждый день. Самое замечательное в системах счисления заключается в том, что нет ничего, что заставляло бы вас иметь 10 разных значений в цифре. Наша система счисления по основанию 10 и , вероятно, выросла из-за того, что у нас 10 пальцев, но если бы мы эволюционировали до восьми пальцев, у нас, вероятно, была бы система счисления с основанием 8. У вас могут быть системы счисления с любым основанием. На самом деле, есть много веских причин использовать разные базы в разных ситуациях.

Компьютеры работают в системе счисления с основанием 2, также известной как двоичная система счисления (точно так же, как система счисления с основанием 10 известна как десятичная система счисления). Узнайте, почему и как это работает, в следующем разделе.

Функции и операторы битовой строки

Эта документация предназначена для неподдерживаемой версии PostgreSQL.
Вы можете просмотреть ту же страницу для
Текущий
версия или одна из других поддерживаемых версий, перечисленных выше.

В этом разделе описаны функции и операторы для исследования и управления битовыми строками, то есть значениями типов bit и bit, которые меняются. Помимо обычных операторов сравнения, можно использовать операторы, показанные в Таблице 9-11. Операнды битовой строки &, | и # должны быть одинаковой длины. При битовом сдвиге исходная длина строки сохраняется, как показано в примерах.

Таблица 9-11. Операторы битовой строки

Оператор Описание Пример Результат
|| конкатенация B'10001 '|| B'011 ' 10001011
и побитовое И B'10001 'и B'01101' 00001
| побитовое ИЛИ B'10001 '| B'01101 ' 11101
# побитовое XOR B'10001 '# B'01101' 11100
~ побитовое НЕ ~ B'10001 ' 01110
<< побитовый сдвиг влево B'10001 '<< 3 01000
>> побитовый сдвиг вправо В'10001 '>> 2 00100

Следующие стандартные функции SQL работают с битовыми строками, а также с символьными строками: длина , длина бит , длина_октета , позиция , подстрока , оверлей .

Следующие функции работают как с битовыми строками, так и с двоичными строками: get_bit , set_bit . При работе с битовой строкой эти функции нумеруют первый (крайний левый) бит строки как бит 0.

Кроме того, можно приводить целые значения к типу bit и от него. Некоторые примеры:

 44 :: бит (10)  0000101100 
44 :: бит (3)  100 
литой (-44 как бит (12))  111111010100 
'1110' :: бит (4) :: целое число  14  

Обратите внимание, что преобразование только в «бит» означает преобразование в бит (1), поэтому будет доставлен только младший значащий бит целого числа.

Примечание: Преобразование целого числа в бит (n) копирует самые правые n битов. Приведение целого числа к ширине битовой строки шире, чем само целое число, будет расширяться по знаку слева.

O.3 - Битовые манипуляции с побитовыми операторами и битовыми масками

Автор Alex 8 сентября 2015 г. | последнее изменение: nascardriver 6 декабря 2020 г.

В предыдущем уроке о побитовых операторах (O.2 - Побитовые операторы) мы обсудили, как различные побитовые операторы применяют логические операторы к каждому биту в операндах.Теперь, когда мы понимаем, как они работают, давайте посмотрим, как они используются чаще.

Битовые маски

Чтобы манипулировать отдельными битами (например, включать или выключать их), нам нужен способ идентифицировать конкретные биты, которыми мы хотим манипулировать. К сожалению, побитовые операторы не умеют работать с битовыми позициями. Вместо этого они работают с битовыми масками.

Битовая маска - это предопределенный набор битов, который используется для выбора конкретных битов, которые будут изменены при последующих операциях.

Рассмотрим случай из реальной жизни, когда вы хотите покрасить оконную раму. Если не проявить осторожность, рискуете покрасить не только оконную раму, но и само стекло. Вы можете купить малярный скотч и приклеить его к стеклу и другим частям, которые вы не хотите красить. Затем, когда вы рисуете, малярная лента блокирует попадание краски на все, что вы не хотите рисовать. В конце концов, окрашиваются только немаскированные части (те части, которые вы хотите покрасить).

Битовая маска, по сути, выполняет ту же функцию для битов - битовая маска блокирует побитовые операторы от прикосновения к битам, которые мы не хотим изменять, и разрешает доступ к тем, которые мы действительно хотим изменить.

Давайте сначала рассмотрим, как определить несколько простых битовых масок, а затем мы покажем вам, как их использовать.

Определение битовых масок в C ++ 14

Простейший набор битовых масок - это определение одной битовой маски для каждой битовой позиции. Мы используем нули, чтобы замаскировать биты, которые нам не нужны, и единицы, чтобы обозначить биты, которые мы хотим изменить.

Хотя битовые маски могут быть литералами, они часто определяются как символьные константы, поэтому им можно дать осмысленное имя и легко использовать повторно.

Поскольку C ++ 14 поддерживает двоичные литералы, определить эти битовые маски очень просто:

#include

constexpr std :: uint_fast8_t mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: uint_fast8_t mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: uint_fast8_t mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: uint_fast8_t mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: uint_fast8_t mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: uint_fast8_t mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: uint_fast8_t mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: uint_fast8_t mask7 {0b1000'0000}; // представляет бит 7

Теперь у нас есть набор символьных констант, представляющих каждую битовую позицию.Мы можем использовать их для манипулирования битами (что мы вскоре покажем, как это сделать).

Определение битовых масок в C ++ 11 или более ранней версии

Поскольку C ++ 11 не поддерживает двоичные литералы, мы должны использовать другие методы для установки символьных констант. Для этого есть два хороших метода. Менее понятным, но более распространенным является использование шестнадцатеричного числа. Если вам нужно напомнить о шестнадцатеричной системе счисления, еще раз посетите урок 4.13 - Литералы.

constexpr std :: uint_fast8_t mask0 {0x1}; // шестнадцатеричный код 0000 0001

constexpr std :: uint_fast8_t mask1 {0x2}; // шестнадцатеричный код 0000 0010

constexpr std :: uint_fast8_t mask2 {0x4}; // шестнадцатеричный код 0000 0100

constexpr std :: uint_fast8_t mask3 {0x8}; // шестнадцатеричный код 0000 1000

constexpr std :: uint_fast8_t mask4 {0x10}; // шестнадцатеричный код 0001 0000

constexpr std :: uint_fast8_t mask5 {0x20}; // шестнадцатеричный код 0010 0000

constexpr std :: uint_fast8_t mask6 {0x40}; // шестнадцатеричное значение 0100 0000

constexpr std :: uint_fast8_t mask7 {0x80}; // шестнадцатеричный для 1000 0000

Это может быть трудно прочитать.Один из способов упростить задачу - использовать оператор сдвига влево, чтобы немного сместиться в нужное место:

constexpr std :: uint_fast8_t mask0 {1 << 0}; // 0000 0001

constexpr std :: uint_fast8_t mask1 {1 << 1}; // 0000 0010

constexpr std :: uint_fast8_t mask2 {1 << 2}; // 0000 0100

constexpr std :: uint_fast8_t mask3 {1 << 3}; // 0000 1000

constexpr std :: uint_fast8_t mask4 {1 << 4}; // 0001 0000

constexpr std :: uint_fast8_t mask5 {1 << 5}; // 0010 0000

constexpr std :: uint_fast8_t mask6 {1 << 6}; // 0100 0000

constexpr std :: uint_fast8_t mask7 {1 << 7}; // 1000 0000

Немного тестируем (включен или выключен)

Теперь, когда у нас есть набор битовых масок, мы можем использовать их вместе с переменной битового флага для управления нашими битовыми флагами.

Чтобы определить, включен или выключен бит, мы используем побитовое И в сочетании с битовой маской для соответствующего бита:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

#include

#include

int main ()

{

constexpr std :: uint_fast8_t mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: uint_fast8_t mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: uint_fast8_t mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: uint_fast8_t mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: uint_fast8_t mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: uint_fast8_t mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: uint_fast8_t mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: uint_fast8_t mask7 {0b1000'0000}; // представляет бит 7

std :: uint_fast8_t flags {0b0000'0101}; // Размер 8 бит означает место для 8 флагов

std :: cout << "бит 0 равен" << ((flags & mask0)? "On \ n": "off \ n");

std :: cout << "бит 1 равен" << ((flags & mask1)? "On \ n": "off \ n");

возврат 0;

}

Это отпечатки:

 бит 0 включен
бит 1 выключен
 

Установка бита

Чтобы установить (включить) бит, мы используем побитовое ИЛИ равно (оператор | =) в сочетании с битовой маской для соответствующего бита:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

#include

#include

int main ()

{

constexpr std :: uint_fast8_t mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: uint_fast8_t mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: uint_fast8_t mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: uint_fast8_t mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: uint_fast8_t mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: uint_fast8_t mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: uint_fast8_t mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: uint_fast8_t mask7 {0b1000'0000}; // представляет бит 7

std :: uint_fast8_t flags {0b0000'0101}; // Размер 8 бит означает место для 8 флагов

std :: cout << "бит 1 равен" << ((flags & mask1)? "On \ n": "off \ n");

флагов | = маска1; // включаем бит 1

std :: cout << "бит 1 равен" << ((flags & mask1)? "on \ n": "off \ n");

возврат 0;

}

Это отпечатки:

 бит 1 выключен
бит 1 включен
 

Мы также можем включить несколько бит одновременно, используя побитовое ИЛИ :

флагов | = (маска4 | маска5); // одновременно включаем биты 4 и 5

Сброс бита

Чтобы немного очистить (выключить), мы используем Побитовое И и Побитовое НЕ вместе:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

#include

#include

int main ()

{

constexpr std :: uint_fast8_t mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: uint_fast8_t mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: uint_fast8_t mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: uint_fast8_t mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: uint_fast8_t mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: uint_fast8_t mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: uint_fast8_t mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: uint_fast8_t mask7 {0b1000'0000}; // представляет бит 7

std :: uint_fast8_t flags {0b0000'0101}; // Размер 8 бит означает место для 8 флагов

std :: cout << "бит 2 равен" << ((flags & mask2)? "On \ n": "off \ n");

flags & = ~ mask2; // выключить бит 2

std :: cout << "бит 2 равен" << ((flags & mask2)? "on \ n": "off \ n");

возврат 0;

}

Это отпечатки:

 бит 2 включен
бит 2 выключен
 

Мы можем отключить несколько бит одновременно:

флагов & = ~ (маска4 | маска5); // одновременно выключаем биты 4 и 5

Немного переворачивая

Чтобы переключить битовое состояние, мы используем Bitwise XOR :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

#include

#include

int main ()

{

constexpr std :: uint_fast8_t mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: uint_fast8_t mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: uint_fast8_t mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: uint_fast8_t mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: uint_fast8_t mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: uint_fast8_t mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: uint_fast8_t mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: uint_fast8_t mask7 {0b1000'0000}; // представляет бит 7

std :: uint_fast8_t flags {0b0000'0101}; // Размер 8 бит означает место для 8 флагов

std :: cout << "бит 2 равен" << ((flags & mask2)? "On \ n": "off \ n");

флагов ^ = маска2; // перевернуть бит 2

std :: cout << "бит 2 равен" << ((flags & mask2)? "on \ n": "off \ n");

флагов ^ = маска2; // перевернуть бит 2

std :: cout << "бит 2 равен" << ((flags & mask2)? "on \ n": "off \ n");

возврат 0;

}

Это отпечатки:

 бит 2 включен
бит 2 выключен
бит 2 включен
 

Мы можем перевернуть несколько бит одновременно:

флагов ^ = (маска4 | маска5); // переворачиваем биты 4 и 5 одновременно

Битовые маски и std :: bitset

std :: bitset поддерживает полный набор побитовых операторов.Таким образом, несмотря на то, что для изменения отдельных битов проще использовать функции (проверка, установка, сброс и переворот), вы можете использовать побитовые операторы и битовые маски, если хотите.

Зачем вам это нужно? Функции позволяют изменять только отдельные биты. Побитовые операторы позволяют изменять сразу несколько битов.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

25

26

27

28

29

30

0009 34

#include

#include

#include

int main ()

{

constexpr std :: bitset <8> mask0 {0b0000'0001}; // представляет бит 0

constexpr std :: bitset <8> mask1 {0b0000'0010}; // представляет бит 1

constexpr std :: bitset <8> mask2 {0b0000'0100}; // представляет бит 2

constexpr std :: bitset <8> mask3 {0b0000'1000}; // представляет бит 3

constexpr std :: bitset <8> mask4 {0b0001'0000}; // представляет бит 4

constexpr std :: bitset <8> mask5 {0b0010'0000}; // представляет бит 5

constexpr std :: bitset <8> mask6 {0b0100'0000}; // представляет бит 6

constexpr std :: bitset <8> mask7 {0b1000'0000}; // представляет бит 7

std :: bitset <8> flags {0b0000'0101}; // Размер 8 бит означает место для 8 флагов

std :: cout << "bit 1 is" ​​<< (flags.= (маска1 | маска2); // переворачиваем биты 1 и 2

std :: cout << "бит 1 равен" << (flags.test (1)? "on \ n": "off \ n");

std :: cout << "бит 2 равен" << (flags.test (2)? "On \ n": "off \ n");

флагов | = (маска1 | маска2); // включаем биты 1 и 2

std :: cout << "бит 1 равен" << (flags.test (1)? "on \ n": "off \ n");

std :: cout << "бит 2 равен" << (flags.test (2)? "On \ n": "off \ n");

флагов & = ~ (маска1 | маска2); // отключаем биты 1 и 2

std :: cout << "бит 1 равен" << (flags.тест (1)? «вкл \ п»: «выкл \ п»);

std :: cout << "бит 2 равен" << (flags.test (2)? "On \ n": "off \ n");

возврат 0;

}

Это отпечатки:

 бит 1 выключен
бит 2 включен
бит 1 включен
бит 2 выключен
бит 1 включен
бит 2 включен
бит 1 выключен
бит 2 выключен
 

Создание значимых битовых масок

Присвоение нашим битовым маскам имен «маска1» или «маска2» сообщает нам, какой бит обрабатывается, но не дает нам никаких указаний на то, для чего на самом деле используется этот битовый флаг.

Лучше всего давать вашим битовым маскам полезные имена, чтобы документировать значение ваших битовых флагов. Вот пример из игры, которую мы могли бы написать:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

25

26

#include

#include

int main ()

{

// Определите набор физических / эмоциональных состояний

constexpr std :: uint_fast8_t isHungry {1 << }; // 0000 0001

constexpr std :: uint_fast8_t isSad {1 << 1}; // 0000 0010

constexpr std :: uint_fast8_t isMad {1 << 2}; // 0000 0100

constexpr std :: uint_fast8_t isHappy {1 << 3}; // 0000 1000

constexpr std :: uint_fast8_t isLaughing {1 << 4}; // 0001 0000

constexpr std :: uint_fast8_t isAsleep {1 << 5}; // 0010 0000

constexpr std :: uint_fast8_t isDead {1 << 6}; // 0100 0000

constexpr std :: uint_fast8_t isCrying {1 << 7}; // 1000 0000

std :: uint_fast8_t me {}; // все флаги / параметры отключены для запуска

me | = (isHappy | isLaughing); // Я счастлив и смеюсь

меня & = ~ isLaughing; // Я больше не смеюсь

// Запрос нескольких состояний

// (мы будем использовать static_cast , чтобы интерпретировать результаты как логическое значение)

std :: cout << "Я счастлив? "<< static_cast (me & isHappy) << '\ n';

std :: cout << "Я смеюсь?" << static_cast (me & isLaughing) << '\ n';

возврат 0;

}

Вот тот же пример, реализованный с использованием std :: bitset:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

25

26

#include

#include

int main ()

{

// Определение группы физических / эмоциональных состояний

std :: bitset <8> isHungry {0b0000 ' 0001};

std :: bitset <8> isSad {0b0000'0010};

std :: bitset <8> isMad {0b0000'0100};

std :: bitset <8> isHappy {0b0000'1000};

std :: bitset <8> isLaughing {0b0001'0000};

std :: bitset <8> isAsleep {0b0010'0000};

std :: bitset <8> isDead {0b0100'0000};

std :: bitset <8> isCrying {0b1000'0000};

std :: bitset <8> me {}; // все флаги / параметры отключены для запуска

me | = (isHappy | isLaughing); // Я счастлив и смеюсь

меня & = ~ isLaughing; // Я больше не смеюсь

// Запросить несколько состояний (мы используем функцию any (), чтобы увидеть, остались ли какие-то биты)

std :: cout << "Я счастлив?" << ( мне & isHappy).any () << '\ n';

std :: cout << "Я смеюсь?" << (me & isLaughing) .any () << '\ n';

возврат 0;

}

Здесь два примечания. Во-первых, std :: bitset не имеет удобной функции, позволяющей запрашивать биты с использованием битовой маски. Поэтому, если вы хотите использовать битовые маски, а не позиционные индексы, вам придется использовать Bitwise AND для запроса битов. Во-вторых, мы используем функцию any (), которая возвращает истину, если какие-либо биты установлены, и ложь в противном случае, чтобы увидеть, остается ли запрошенный нами бит включенным или выключенным.

Когда битовые флаги наиболее полезны?

Проницательные читатели могут заметить, что приведенные выше примеры на самом деле не экономят память. 8 логических значений обычно занимают 8 байтов. Но в приведенных выше примерах используется 9 байтов (8 байтов для определения битовых масок и 1 байт для переменной флага)!

Битовые флаги имеют наибольший смысл, когда у вас много идентичных переменных-флагов. Например, в приведенном выше примере представьте, что вместо одного человека (меня) у вас было 100. Если вы использовали 8 логических значений на человека (по одному для каждого возможного состояния), вы использовали бы 800 байт памяти.С битовыми флагами вы должны использовать 8 байтов для битовых масок и 100 байтов для переменных битовых флагов, в общей сложности 108 байтов памяти - примерно в 8 раз меньше памяти.

Для большинства программ объем памяти, сохраненный с использованием битовых флагов, не стоит дополнительных сложностей. Но в программах, где есть десятки тысяч или даже миллионы похожих объектов, использование битовых флагов может существенно сократить использование памяти. Это полезная оптимизация, если она вам понадобится.

Есть еще один случай, когда битовые флаги и битовые маски могут иметь смысл.Представьте, что у вас есть функция, которая может принимать любую комбинацию из 32 различных вариантов. Один из способов написать эту функцию - использовать 32 отдельных логических параметра:

void someFunction (bool option1, bool option2, bool option3, bool option4, bool option5, bool option6, bool option7, bool option8, bool option9, bool option10, bool option11, bool option12, bool option13, bool option14, bool option15, bool option16, bool option17, bool option18, bool option19, bool option20, bool option21, bool option22, bool option23, bool option24, bool option25, bool option26, bool option27, bool option28, bool option29, bool option30, bool option31, bool option32 );

Надеюсь, вы дадите своим параметрам более описательные имена, но суть здесь в том, чтобы показать вам, насколько неприятно длинный список параметров.

Затем, когда вы хотите вызвать функцию с параметрами 10 и 32, установленными в значение true, вам нужно будет сделать это следующим образом:

someFunction (ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, истина, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, ложь, истина);

Это до смешного сложно читать (это параметр 9, 10 или 11, установленный в значение true?), А также означает, что вы должны помнить, какой аргумент соответствует какой опции (устанавливает «флаг редактирования» 9, 10, или 11-й параметр?) Это также может быть не очень производительным, поскольку каждый вызов функции должен копировать 32 логических значения из вызывающего в функцию.

Вместо этого, если вы определили функцию с помощью битовых флагов, например:

void someFunction (параметры std :: bitset <32>);

Затем вы можете использовать битовые флаги для передачи только тех параметров, которые вам нужны:

someFunction (option10 | option32);

Это не только намного удобнее для чтения, но и, вероятно, будет более производительным, поскольку включает всего 2 операции (одну побитовое ИЛИ и одну копию параметра).

Это одна из причин, по которой OpenGL, хорошо зарекомендовавшая себя библиотека трехмерной графики, решила использовать параметры битовых флагов вместо многих последовательных логических параметров.

Вот пример вызова функции из OpenGL:

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // очищаем буфер цвета и глубины

GL_COLOR_BUFFER_BIT и GL_DEPTH_BUFFER_BIT - это битовые маски, определенные следующим образом (в gl2.в):

#define GL_DEPTH_BUFFER_BIT 0x00000100

#define GL_STENCIL_BUFFER_BIT 0x00000400

#define GL_COLOR_BUFFER_BIT 0x00004000

Битовые маски, включающие несколько битов

Хотя битовые маски часто используются для выбора одного бита, их также можно использовать для выбора нескольких битов. Давайте посмотрим на более сложный пример, в котором мы это делаем.

Устройства цветного отображения, такие как телевизоры и мониторы, состоят из миллионов пикселей, каждый из которых может отображать одну цветовую точку. Цветная точка состоит из трех световых лучей: красного, зеленого и синего (RGB). Изменяя интенсивность цветов, можно получить любой цвет в цветовом спектре. Обычно количество R, G и B для данного пикселя представлено 8-битовым целым числом без знака. Например, красный пиксель будет иметь R = 255, G = 0, B = 0. У фиолетового пикселя R = 255, G = 0, B = 255.Средне-серый пиксель будет иметь R = 127, G = 127, B = 127.

При присвоении значений цвета пикселю, помимо R, G и B, часто используется 4-е значение, называемое A. «A» означает «альфа», и он определяет, насколько прозрачным будет цвет. Если A = 0, цвет полностью прозрачный. Если A = 255, цвет непрозрачный.

R, G, B и A обычно хранятся как одно 32-битное целое число, с 8 битами, используемыми для каждого компонента:

32-битное значение RGBA
бит 31-24 бит 23-16 бит 15-8 бит 7-0
RRRRRRRR GGGGGGGG BBBBBBBB AAAAAAAA
красный зеленый синий альфа

Следующая программа просит пользователя ввести 32-битное шестнадцатеричное значение, а затем извлекает 8-битные значения цвета для R, G, B и A.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

21

22

23

24

25

26

27

28

29

30

#include

#include

int main ()

{

constexpr std :: uint_fast32_t redBits {0xFF000000};

constexpr std :: uint_fast32_t greenBits {0x00FF0000};

constexpr std :: uint_fast32_t blueBits {0x0000FF00};

constexpr std :: uint_fast32_t alphaBits {0x000000FF};

std :: cout << "Введите 32-битное значение цвета RGBA в шестнадцатеричном формате (например,грамм. FF7F3300): ";

std :: uint_fast32_t pixel {};

std :: cin >> std :: hex >> pixel; // std :: hex позволяет нам читать в шестнадцатеричном формате

/ / используйте побитовое И для выделения красных пикселей,

// затем сдвиньте значение вправо в младшие 8 бит

std :: uint_fast8_t red {static_cast ((pixel & redBits) >> 24)};

std :: uint_fast8_t зеленый {static_cast ((pixel & greenBits) >> 16)};

std :: uint_fast8_t синий {static_cast ((pixel & blueBits) >> 8)};

std :: uint_fast8_t alpha {static_cast (pixel & alphaBits)};

std :: cout << "Ваш цвет содержит: \ n";

std :: cout << std :: hex; // выводим следующие значения в шестнадцатеричном формате

std :: cout << static_cast (red) << "red \ n";

std :: cout << static_cast (зеленый) << "зеленый \ n";

std :: cout << static_cast (синий) << "синий \ n" ;

std :: cout << static_cast (alpha) << "alpha \ n";

возврат 0;

}

Это дает результат:

 Введите 32-битное значение цвета RGBA в шестнадцатеричном формате (например,грамм. FF7F3300): FF7F3300
Ваш цвет содержит:
ff красный
7f зеленый
33 синий
0 альфа
 

В приведенной выше программе мы используем поразрядное И для запроса набора из 8 интересующих нас битов, а затем сдвигаем их вправо в 8-битное значение, чтобы мы могли распечатать их как шестнадцатеричные значения.

Сводка

Обобщая, как устанавливать, очищать, переключать и запрашивать битовые флаги:

Для запроса состояний битов мы используем побитовое И :

если (флаги и опция4)... // если установлен option4, сделать что-нибудь

Для установки битов (включения) используем побитовое ИЛИ :

флагов | = option4; // включаем опцию 4.

флагов | = (option4 | option5); // включаем варианты 4 и 5.

Чтобы очистить биты (выключить), мы используем побитовое И с побитовое НЕ :

flags & = ~ option4; // отключаем опцию 4

flags & = ~ (option4 | option5); // отключаем варианты 4 и 5

Чтобы перевернуть битовые состояния, мы используем побитовое исключающее ИЛИ :

флагов ^ = option4; // переключаем option4 с on на off или наоборот

flags ^ = (option4 | option5); // переворачиваем варианты 4 и 5

Время викторины

Не используйте std :: bitset в этой викторине.Мы используем для печати только std :: bitset .

Дана следующая программа:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

18

19

20

#include

#include

#include

int main ()

{

constexpr std :: uint_fast8_t option_viewed {0x01};

constexpr std :: uint_fast8_t option_edited {0x02};

constexpr std :: uint_fast8_t option_favorited {0x04};

constexpr std :: uint_fast8_t option_shared {0x08};

constexpr std :: uint_fast8_t option_deleted {0x10};

std :: uint_fast8_t myArticleFlags {option_favorited};

//...

std :: cout << std :: bitset <8> {myArticleFlags} << '\ n';

возврат 0;

}

a) Напишите строку кода, чтобы сделать статью просматриваемой.
Ожидаемый результат:

 00000101
 

Показать решение

myArticleFlags | = option_viewed;

б) Напишите строку кода, чтобы проверить, была ли удалена статья.

Показать решение

if (myArticleFlags & option_deleted) ...

c) Напишите строку кода, чтобы удалить статью как избранную.
Ожидаемый результат (при условии, что вы прошли тест (а)):

 00000001
 

Показать решение

myArticleFlags & = ~ option_favorited;

1d) Дополнительный кредит: почему следующие две строки идентичны?

myflags & = ~ (option4 | option5); // отключаем опции 4 и 5

myflags & = ~ option4 & ~ option5; // отключаем варианты 4 и 5

Показать решение

Закон Де Моргана гласит, что если мы распространяем НЕ, нам нужно поменять ИЛИ и И на другое.Таким образом, ~ (option4 | option5) становится ~ option4 & ~ option5 .

Как использовать побитовые операторы. Изучите битовые манипуляции | Девин Сони 👑

Если не совсем понятно, зачем вам нужны эти операторы - вы не одиноки. Когда я впервые узнал о них, они не казались такими уж полезными.

Но во многих областях, таких как встроенное программирование, системное программирование и сети, их можно увидеть в решениях множества различных проблем.

Теперь давайте рассмотрим некоторые варианты использования побитовых операторов и, надеюсь, вы начнете их больше ценить.

Представление данных

Побитовые операторы - отличный способ очень эффективно использовать пространство при представлении данных.

Представьте себе следующую ситуацию:

Вы разработчик компьютерной игры и хотите периодически сохранять положение мыши игроков, пока они играют в игру. Для этого вы каждую минуту отправляете позиции курсора игроков на игровой сервер. Игра работает в фиксированном размере окна 1000 x 1000.

Наивный способ сделать это - просто отправить обратно два целых числа данных на ваш сервер (одно для координаты x и одно для координаты y) для каждого снимка.Обычно целые числа составляют 32 бита, поэтому это означает отправку 64 бита данных.

Однако мы можем сделать это намного более компактным, используя поразрядные операторы.

Так как каждая координата не более 1000, это означает, что нам нужно только 10 бит для хранения каждого числа, поскольку двоичный код 1111111111 равен 1023, что больше 1000. Это означает, что на самом деле нам нужно всего 20 бит.

Мы можем сохранить эти 20 бит в одном 32-битном целом числе - крайние правые 10 бит будут представлять координату x, следующие 10 бит будут представлять координату y, а оставшиеся 12 бит будут просто нулевыми.

Как только данные достигнут внутреннего сервера, мы можем извлечь каждое число с помощью правого сдвига и операторов ИЛИ.

Полезные свойства base-2

Находясь в base-2, вы получаете несколько удобных свойств, которые позволяют легко ответить на некоторые общие вопросы.

Например, мы можем очень легко определить, является ли число степенью 2.

У любого числа, которое является степенью 2, только крайний левый бит будет установлен в 1, а все остальные будут равны 0.

Мы можем быстро определить, следует ли этому шаблону число x , проверив, соответствует ли (x & (x - 1)) == 0 .y .

Найдите единственное целое число в массиве, которое не встречается дважды

Мы выполняем операцию XOR для каждого элемента в массиве. Поскольку число XOR’d с самим собой равно 0, значение этой промежуточной суммы после того, как мы пройдемся по всему массиву, будет целым числом, которое не встречается дважды.

Бит / байтовая двойственность - документация Construct 2.10

История

В Construct 1.XX синтаксический анализ и построение выполнялись на битовом уровне: все данные были преобразованы в строку из единиц и нулей, так что вы действительно могли работать с битовыми полями.Каждая конструкция работала с битами, за исключением некоторых (названных ByteXXX), которые работали с целыми октетами. Это упростило работу с отдельными битами, такими как флаги заголовка TCP, 7-битные символы ASCII или поля, которые не были выровнены по границе байта (полубайты и др.).

Этот подход был простым и гибким, но имел два основных недостатка:

  • Большинство данных выровнены по байтам (за очень немногими исключениями)
  • Накладные расходы слишком велики

Поскольку конструкции работали с битами, данные нужно было сначала преобразовать в битовую строку, что означало, что вы должны были хранить весь набор данных в памяти.Не только это, но вы фактически содержали в 8 раз больше исходных данных (это была битовая строка). Согласно некоторым проведенным мною тестам, вы были ограничены файлами размером около 50 МБ (и это было медленно из-за перебоев на странице).

Итак, начиная с Construct 2.XX, все конструкции работают с байтами:

  • Меньше потребление памяти
  • Нет ненужных покрытий между байтами и битами
  • Может полагаться на встроенный структурный модуль Python для числовой упаковки / распаковки (быстрее и проверено)
  • Может напрямую анализировать и создавать файловые объекты (без буферизации в памяти)

Но как мы должны работать с необработанными битами? Единственное отличие состоит в том, что мы должны явно заявить, что: определенные поля, такие как BitsInteger (Bit Nibble Octet являются экземплярами BitsInteger), обрабатывают синтаксический анализ и построение битовых строк.Также есть несколько полей, таких как Struct и Flag, которые работают как с байтовыми, так и с битовыми строками.

BitStruct

BitStruct - это последовательность конструкций, которые анализируются / строятся в указанном порядке, как и обычные структуры. Разница в том, что BitStruct работает с битами, а не с байтами. При синтаксическом разборе BitStruct данные сначала преобразуются в битовый поток (поток \ x01 и \ x00), и только затем они передаются в подконструкции. Предполагается, что подконструкции будут работать с битами, а не с байтами.Для справки посмотрите код ниже:

 >>> d = BitStruct (
...     "флаг,
... "б" / Клев,
... "c" / BitsInteger (10),
... "d" / Padding (1),
...)
>>> d.parse (b "\ xbe \ xef")
Контейнер (a = True, b = 7, c = 887, d = None)
>>> d.sizeof ()
2
 

BitStruct на самом деле является просто оболочкой для Bitwise вокруг Struct .

Поля, которые выполняют обе функции

Некоторые простые поля (такие как Flag Padding Pass Terminated) игнорируют гранулярность данных, с которыми они работают.Фактическая степень детализации зависит от включающих слоев. То же самое относится к классам, которые являются оболочками или адаптерами, такими как Enum EnumFlags. Эти классы не заботятся о гранулярности, потому что они не взаимодействуют с потоком, его подконструкциями.

Вот фрагмент кода, который работает с байтами:

 >>> d = Struct (
... Прокладка (2),
... "x" / Флаг,
... Прокладка (5),
...)
>>> d.build (dict (x = 5))
б '\ x00 \ x00 \ x01 \ x00 \ x00 \ x00 \ x00 \ x00'
>>> d.sizeof ()
8
 

А вот фрагмент кода, который работает с битами.Единственное отличие - BitStruct вместо обычного Struct:

 >>> d = Побитовое (Struct (
... Прокладка (2),
... "x" / Флаг,
... Прокладка (5),
...))
>>> d.build (dict (x = 5))
б ''
>>> d.sizeof ()
1
 

Итак, в отличие от «классической конструкции», нет необходимости в BytePadding и BitPadding. Если Padding заключен в BitStruct, он работает с битами, в противном случае он работает с байтами.

Поля, которые не работают и выходят из строя

Следующие классы могут не работать в Bitwise Bytewise в зависимости от некоторых обстоятельств.Фактически этот раздел также относится к ByteSwapped BitsSwapped. Эти 4 макроса разрешаются либо в Transformed, либо в Restreamed, в зависимости от того, имеет ли subcon фиксированный размер и, следовательно, данные могут быть предварительно загружены полностью. Если да, то он превращается в Transformed и должен работать нормально, это не так, тогда он превращается в Restreamed, который использует RestreamedBytesIO, который имеет несколько ограничений в его реализации. Пробег может отличаться.

Те действительно используют поиск потока или сообщение (или и то, и другое):

  • Жадный диапазон
  • Союз
  • Выбрать
  • Padded (действительно работает)
  • Выровнен (реально работает)
  • Указатель
  • Подглядывать
  • искать
  • Скажите
  • RawCopy
  • с префиксом (реально работает)
  • PrefixedArray (действительно работает)
  • NullTerminated (действительно работает, если потребление = False)
  • LazyStruct
  • LazyArray

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *