Статьи

Понимание побитовых операторов

Побитовые операторы – это те странно выглядящие операторы, которые могут выглядеть трудно понять … но не больше! Эта легкая в использовании статья поможет вам понять, что они из себя представляют и как их использовать, а также пара практических примеров, которые покажут вам, когда и зачем они вам нужны.


Побитовые операторы – это операторы (например, +, *, && и т. Д.), ints работают с ints и uints на двоичном уровне. Это означает, что они смотрят непосредственно на двоичные цифры или биты целого числа. Все это звучит страшно, но на самом деле побитовые операторы довольно просты в использовании и также весьма полезны!

Тем не менее, важно, чтобы вы понимали двоичные числа и шестнадцатеричные числа. Если вы этого не сделаете, пожалуйста, ознакомьтесь с этой статьей – она ​​действительно поможет вам! Ниже приведено небольшое приложение, которое позволит вам опробовать различные побитовые операторы.

Не волнуйтесь, если вы еще не поняли, что происходит, скоро все станет ясно …


Давайте посмотрим на побитовые операторы, которые поставляет AS3. Многие другие языки очень похожи (например, JavaScript и Java имеют практически идентичные операторы):

  • & (побитовое И)
  • | (побитовое ИЛИ)
  • ~ (поразрядно НЕ)
  • ^ (побитовый XOR)
  • << (битовое смещение влево)
  • >> (сдвиг вправо вправо)
  • >>> (битовое беззнаковое смещение вправо)
  • & = (побитовое И присвоение)
  • | = (побитовое ИЛИ присваивание)
  • ^ = (побитовое присвоение XOR)
  • << = (битовое смещение влево и присвоение)
  • >> = (битовое правое смещение и присвоение)
  • >>> = (битовое беззнаковое правое смещение и присвоение)

Из этого следует сделать несколько вещей: во-первых, некоторые побитовые операторы похожи на те, что вы использовали ранее (& vs. &&, | vs. ||). Это потому, что они чем-то похожи.

Во-вторых, большинство побитовых операторов имеют сложную форму присваивания . Это так же, как вы можете использовать + и + =, – и – = и т. Д.


Сначала вверх: побитовый оператор И, &. Однако, быстрый ints -ап: как правило, ints и uints занимают 4 байта или 32 бита. Это означает, что каждый int или uint хранится в виде 32 двоичных цифр. Ради этого урока мы будем иногда притворяться, что ints и uints только 1 байт и имеют только 8 двоичных цифр.

Оператор & сравнивает каждую двоичную цифру из двух целых чисел и возвращает новое целое число с 1, где оба числа имели 1 и 0 где-либо еще. Диаграмма стоит тысячи слов, так что вот один, чтобы прояснить ситуацию. Это означает выполнение 37 & 23 , что равно 5 .

Пример побитового оператора А

Обратите внимание, как сравниваются каждая двоичная цифра 37 и 23, и результат имеет 1, где 37 и 23 имеет 1, а результат имеет 0 в противном случае.

Распространенный способ думать о двоичных разрядах как true или false . То есть 1 эквивалентно true и 0 эквивалентно false . Это делает оператор & более понятным.

Когда мы сравниваем два логических значения, мы обычно делаем boolean1 && boolean2 . Это выражение верно только в том случае, если оба значения – boolean1 и boolean2 – true. Таким же образом integer1 & integer2 эквивалентны, так как оператор & выводит только 1, когда обе двоичные цифры наших двух целых чисел равны 1.

Вот таблица, которая представляет эту идею:

Побитовое И таблица

Небольшое использование оператора & заключается в проверке, является ли число четным или нечетным. Для целых чисел мы можем просто проверить самый правый бит (также называемый младшим значащим битом), чтобы определить, является ли целое число нечетным или четным. Это происходит потому, что при преобразовании в основание 10 самый правый бит представляет 2 0 или 1. Когда самый правый бит равен 1, мы знаем, что наше число нечетно, поскольку мы добавляем 1 к группе степеней двух, которая всегда будет четной , Когда самый правый бит равен 0, мы знаем, что наше число будет четным, поскольку оно просто состоит из суммирования четных чисел.

Вот пример:

1
2
3
4
5
6
7
8
9
var randInt:int = int(Math.random()*1000);
if(randInt & 1)
{
    trace(“Odd number.”);
}
else
{
    trace(“Even number.”);
}

На моем компьютере этот метод был примерно на 66% быстрее, чем использование randInt % 2 для проверки четных и нечетных чисел. Это значительное повышение производительности!


Далее идет побитовый оператор ИЛИ, |. Как вы уже догадались, | оператор к || оператор как оператор & является оператором &&. | Оператор сравнивает каждую двоичную цифру по двум целым числам и возвращает 1, если любое из них равно 1. Опять же, это похоже на || оператор с логическими значениями.

Побитовое ИЛИ таблица

Давайте рассмотрим тот же пример, что и раньше, за исключением использования | оператор вместо оператора &. Мы сейчас делаем 37 | 23 37 | 23 что равно 55:

Побитовое ИЛИ таблица

Мы можем воспользоваться преимуществами & и | операторы, которые позволяют нам передавать несколько опций функции в одном int .

Давайте посмотрим на возможную ситуацию. Мы создаем класс всплывающих окон. Внизу у нас может быть кнопка Да, Нет, Ладно или Отмена или любая их комбинация – как мы должны это сделать? Вот трудный путь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class PopupWindow extends Sprite
{
    // Variables, Constructor, etc…
 
 
    public static void showPopup(yesButton:Boolean, noButton:Boolean, okayButton:Boolean, cancelButton:Boolean)
    {
        if(yesButton)
        {
            // add YES button
        }
         
        if(noButton)
        {
            // add NO Button
        }
        // and so on for the rest of the buttons
    }
}

Это ужасно? Нет. Но плохо, если вы программист, вынуждены искать порядок аргументов при каждом вызове функции. Это также раздражает – например, если вы хотите показать только кнопку «Отмена», вы должны установить для всех остальных Booleans значение false .

Давайте использовать то, что мы узнали о & и | чтобы сделать лучшее решение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class PopupWindow extends Sprite
{
    public static const YES:int = 1;
    public static const NO:int = 2;
    public static const OKAY:int = 4;
    public static const CANCEL:int = 8;
 
    public static void showPopup(buttons:int)
    {
        if(buttons & YES)
        {
            // add YES button
        }
         
        if(buttons & NO)
        {
            // add NO button
        }
    }
}

Как бы программист вызвал эту функцию, чтобы отображались кнопки «Да», «Нет» и «Отмена»? Как это:

1
PopupWindow.show(PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL);

В чем дело? Важно отметить, что наши константы во втором примере являются степенями двух. Итак, если мы посмотрим на их двоичные формы, мы заметим, что все они имеют одну цифру, равную 1, а остальные равны 0. На самом деле, они имеют разные цифры, равные 1. Это означает, что независимо от того, как мы объединяем их с |, каждая комбинация даст нам уникальный номер. Глядя на это по-другому, результат нашего | оператор будет двоичным числом с 1, где у наших опций было 1.

Для нашего текущего примера у нас есть PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL который эквивалентен 1 | 2 | 8 1 | 2 | 8 1 | 2 | 8 который переписан в двоичном виде это 00000001 | 00000010 | 00001000 00000001 | 00000010 | 00001000 00000001 | 00000010 | 00001000 который дает нам результат 00001011 .

Теперь в нашей функции showPopup() мы используем &, чтобы проверить, какие параметры были переданы. Например, когда мы проверяем buttons & YES , все биты в YES равны 0, кроме самого правого. Итак, мы по существу проверяем, является ли самый правый бит в кнопках 1 или нет. Если это так, buttons & YES не будут равны 0 и все в операторе if будет выполнено. И наоборот, если самый правый бит в кнопках равен 0, buttons & YES будут равны 0, и оператор if не будет выполнен.


Битовый оператор NOT немного отличается от двух, которые мы рассматривали до сих пор. Вместо того, чтобы брать целое число с каждой стороны, оно берет целое число только после него. Это так же, как! оператор, и, что неудивительно, делает то же самое. На самом деле так же, как! переключает логическое значение с true на false или наоборот, оператор ~ инвертирует каждую двоичную цифру в целое число: от 0 до 1 и от 1 до 0:

Побитовое ИЛИ таблица

Быстрый пример. Скажем, у нас есть целое число 37, или 00100101. Тогда ~ 37 равно 11011010. Каково значение 10 для этого? Что ж…


Теперь самое интересное начинается! Мы собираемся поближе взглянуть на двоичные числа на компьютере. Начнем с uint . Как упоминалось ранее, uint обычно составляет 4 байта или 32 бита, то есть 32 двоичных разряда. Это легко понять: чтобы получить значение 10, мы просто регулярно конвертируем число в 10. Мы всегда получим положительное число.

Но как насчет int ? Он также использует 32 бита, но как хранить отрицательные числа? Если вы догадались, что первая цифра используется для хранения знака, вы на правильном пути. Давайте посмотрим на систему дополнений для хранения двоичных чисел. Хотя здесь мы не будем вдаваться во все детали, используется система из двух дополнений, потому что она упрощает двоичную арифметику.

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

Дополнение к двум из 37

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

Хорошо, давайте возьмем простое выражение 37 + -37 . Мы все знаем, что это должно равняться 0, и когда мы добавляем 37 к дополнению к его двум, вот что мы получаем:

37 + -37 в двоичном

Обратите внимание, что поскольку наши целые числа содержат только восемь двоичных цифр, 1 в нашем результате отбрасывается, и мы получаем 0, как и должны.

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

Хотите попробовать это сами? Добавить trace(~37+1); в файл AS3, затем скомпилируйте и запустите его. Вы увидите -37 напечатано, как и должно быть.

Есть также небольшой ярлык, чтобы сделать это вручную: начиная справа, работайте влево, пока не достигнете 1. Отразите все биты слева от этого первого 1.

Дополнение к 37 из 37

Когда мы смотрим на двоичное число со знаком (другими словами, оно может быть отрицательным, int не uint ), мы можем посмотреть на крайнюю левую цифру, чтобы определить, является ли она отрицательной или положительной. Если это 0, то число положительное, и мы можем преобразовать в основание 10, просто вычислив его значение 10. Если крайний левый бит равен 1, то число отрицательное, поэтому мы берем двоичное дополнение числа, чтобы получить его положительное значение, а затем просто добавляем отрицательный знак.

Например, если у нас есть 11110010, мы знаем, что это отрицательное число. Мы можем найти дополнение к двум, щелкнув все цифры слева от крайней правой 1, что дает нам 00001110. Это равно 13, поэтому мы знаем, что 11110010 равно -13.


Мы вернулись к побитовым операторам, и далее идет побитовый оператор XOR. Этому эквивалентному логическому оператору не существует.

Оператор ^ похож на & и | операторы в том, что он принимает int или uint с обеих сторон. Когда он вычисляет результирующее число, он снова сравнивает двоичные цифры этих чисел. Если один или другой равен 1, он вставит 1 в результат, в противном случае он вставит 0. Отсюда и название XOR, или «exclusive or».

Битовая таблица XOR

Давайте посмотрим на наш обычный пример:

Пример побитового XOR

Оператор ^ действительно имеет применение – он особенно хорош для переключения двоичных цифр – но мы не будем рассматривать практические приложения в этой статье.


Теперь мы работаем с операторами сдвига битов, в частности, с оператором сдвига битов влево.

Они работают немного иначе, чем раньше. Вместо сравнения двух целых чисел, таких как &, | и ^ did, эти операторы сдвигают целое число. С левой стороны от оператора находится целое число, которое смещается, а справа – на сколько сместится. Так, например, 37 << 3 смещает число 37 влево на 3 места. Конечно, мы работаем с двоичным представлением 37.

Давайте посмотрим на этот пример (помните, мы просто притворимся, что целые числа имеют только 8 бит вместо 32). Здесь у нас есть число 37, сидящее на его хорошем блоке памяти шириной 8 бит.

Пример побитового сдвига влево

Хорошо, давайте сдвинем все цифры влево на 3, как 37 << 3 будет делать:

Пример побитового сдвига влево

Но теперь у нас есть небольшая проблема – что мы делаем с 3 открытыми битами памяти, куда мы переместили цифры?

Пример побитового сдвига влево

Конечно! Любые пустые места просто заменяются на 0. Мы заканчиваем с 00101000. И это все, что есть в левом сдвиге. Помните, что Flash всегда думает, что результат сдвига влево – это int , а не uint . Поэтому, если вам по какой-то причине понадобится uint , вам придется привести его к uint например: uint(37 << 3) . Этот кастинг на самом деле не меняет какую-либо двоичную информацию, просто то, как Flash ее интерпретирует (дополнение к целым двум).

Интересной особенностью левого смещения битов является то, что он равен умножению числа на два до степени shiftAmount-th. Итак, 37 << 3 == 37 * Math.pow(2,3) == 37 * 8 . Если вместо Math.pow вы можете использовать левый сдвиг, вы увидите значительное увеличение производительности.

Возможно, вы заметили, что двоичное число, которое мы получили, не равно 37 * 8. Это просто из-за того, что мы используем только 8 бит памяти для целых чисел; если вы попробуете это в ActionScript, вы получите правильный результат. Или попробуйте с демо в верхней части страницы!


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

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

Хотя это звучит сложно, в основном это просто сохраняет знак числа, с которого мы начинаем. Итак -8 >> 2 == -2 а 8 >> 2 == 2 . Я бы порекомендовал попробовать это на бумаге самостоятельно.

Поскольку >> является противоположностью <<, неудивительно, что смещение числа вправо равносильно делению его на 2 в степени shiftAmount. Возможно, вы заметили это из приведенного выше примера. Опять же, если вы можете использовать это, чтобы избежать вызова Math.pow , вы получите значительное повышение производительности.


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


Одним из наиболее практичных применений побитовых операторов в Actionscript 3 является работа с цветами, которые обычно хранятся в виде uints .

Стандартный формат для цветов – записать их в шестнадцатеричном формате: 0xAARRGGBB – каждая буква представляет шестнадцатеричное число. Здесь первые две шестнадцатеричные цифры, которые эквивалентны первым восьми двоичным цифрам , представляют нашу альфа или прозрачность. Следующие восемь битов представляют количество красного в нашем цвете (то есть целое число от 0 до 255), следующие восемь – количество зеленого, а последние восемь представляют количество синего в нашем цвете.

Без побитовых операторов очень сложно работать с цветами в этом формате – но с ними это просто!

Задача 1. Найти количество синего в цвете. Используя оператор &, попытайтесь найти количество синего в произвольном цвете.

1
2
3
4
public function findBlueComponent(color:uint):uint
{
    // Your code here!
}

Нам нужен способ «стереть» или замаскировать все остальные данные в color и просто оставить синий компонент. На самом деле это легко! Если мы возьмем color & 0x000000FF – или, проще говоря, color & 0xFF – мы получим только синий компонент.

Пример побитового сдвига влево

Как видно из приведенного выше и вы узнали из описания оператора &, любая двоичная цифра & 0 всегда будет равна 0, а любая двоичная цифра & 1 сохранит свое значение. Так что, если мы замаскируем наш цвет 0xFF, который имеет только 1 с, где синий компонент нашего цвета находится, мы получим только синий компонент.

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

1
2
3
4
public function findRedComponent(color:uint):uint
{
    // Your code here!
}

На самом деле у нас есть два решения этой проблемы. Один будет return (color & 0xFF0000) >> 16; а другой будет return (color >> 16) & 0xFF;

Это очень похоже на Задачу 1, за исключением того, что мы должны изменить наш ответ в какой-то момент.

Задача 3. Найти прозрачность цвета . Используя только один побитовый оператор, попытайтесь найти альфа цвета (целое число от 0 до 255).

1
2
3
4
public function findAlphaComponent(color:uint):uint
{
    // Your code here!
}

Это немного сложнее. Мы должны быть осторожны с тем, какой оператор смещения вправо мы выберем. Поскольку мы работаем с крайними левыми цифрами uint , мы хотим использовать оператор >>>. Итак, наш ответ просто return color >>> 24; ,

Final Challenge: создайте цвет из его компонентов : используя << и | операторы, взять компоненты цвета и объединить их в одну единицу.

1
2
3
4
public function createColor(a:uint, r:uint, g:uint, b:uint):uint
{
    // Your code here!
}

Здесь мы должны переместить каждый компонент в правильное положение, а затем объединить их. Мы хотим, чтобы Flash рассматривал его как целое число без знака, поэтому мы приводим его к uint : return uint((a << 24) | (r << 16) | (g << 8) | b);


Возможно, вы заметили, что я пренебрег объяснением составных побитовых операторов. Представьте, что у нас есть целое число х. Тогда x = x & 0xFF совпадает с x &= 0xFF , x = x | 256 x = x | 256 – это то же самое, что и x |= 256 , и так далее для остальных составных операторов.


Спасибо за чтение этой статьи! Я надеюсь, что теперь вы понимаете побитовые операторы и можете использовать их в своем коде AS3 (или во многих других языках!). Как всегда, если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте их ниже.