Поговорим о битах
Люди начинают спрашивать меня, читая мои записи в блоге - а что это такое "<<" или "&", как оно работает и вообще зачем оно нужно. Сегодня я вам об этом и расскажу.
Статья рассчитана на подрастающих девелоперов, ничего революционного в ней нет =)
Итак, что же творится behind the magic =)
Начнём с простого, вообще поймём, что же такое биты.
Есть байты. Байт - это 8 битов. А бит - это элемент, который может принимать значения 0 или 1. То есть байт - это восемь положений 0 или 1. (прошу прощения за мой очень вольный рассказ). Компьютер вообще работает с двоичной системой счисления, то есть у него есть только цифры 0 и 1. У нас их целых десять - 0,1,2,3,4..9. Поэтому привычная система исчисления для нас десятичная. И вот же проблема: компьютер использует двоичную, а мы привыкли использовать десятичную, как же нам... Да всё нормально, успокойтесь. Сущность числа не меняется от записи его в любой из систем исчисления.
Например, записав во флеше в шестандцатиричной системе 0xFF мы записали число 255. Поэтому делая trace(0xFFFFFF) мы видим какое то странно число, начинающиеся с 16.... Дело в том, что флеш специально выводит нам числа в десятичной системе, как нам удобно. А теперь давайте запишем какое-нибудь число, например "2" в двоичной системе, "родной" для компьютера:
10
Но каждый разряд принимает значения или 0, или 1. Это значит что каждый разряд - это бит. Но чаще люди записывают все 8 разрядов сразу, чтобы видеть тот самый байт.
00000010
Вот он байт. Таким байтом записано число 2 в компьютерном представлении. Конечно, оперировать с битами должно быть куда быстрее - мы будем отдавать компьютеру приказы в его "родной среде", поэтому важно понимать, как они работают и что делают.
Сейчас рассмотрим 3 оператора: |, & и >>, а потом посмотрим что же это дело делает на практике.
Оператор |: это оператор бинарного или. Он сравнивает каждый бит первого байта с соответствующим каждым битом второго байта, и если хотя бы один из них равен единице - на выходе будет единица.
Пример:
2 | 3 = 3
00000010 | 00000011 = 00000011
Важно! Первый бит идёт справа.
Сравниваем первый бит с первым битом: 0 | 1 = 1, т.к. хотя бы один из них равен единице.
Сравниваем второй бит с вторым битом: 1 | 1 = 1, т.к. хотя бы один из них равен единице.
Остальные биты: 0 | 0 = 0, т.к. ни один бит не равен единице.
Оператор &: это оператор бинарного и. Делает то же самое, что и "или", только чтобы на выходе была единица нужно чтобы оба бита были равны единице.
Пример:
2 & 3 = 2
00000010 & 00000011 = 00000010
Сравниваем первый бит с первым битом: 0 & 1 = 0, т.к. оба бита НЕ равны 1.
Сравниваем второй бит со вторым: 1 & 1 = 1, т.к. оба бита равны 1
Остальные: 0 & 0, т.к. оба бита не равны 1.
Оператор >>: это оператор сдвига вправо. Он сдвигает биты. Второй операнд - это на сколько битов нужно сдвинуть это число.
Например:
00001100 >> 1 = 00000110
Мы просто подвинули число целиком на бит вправо, а слева вдвинули ноль. Ещё пример:
11100000 >> 2 = 00111000
Мы сдвинули это число 2 раза вправо, а слева добавили 2 нуля.
Важно понять, что при сдвиге вправо разряды, которые "выбились" уходят в небытие и их уже никак не вернуть.
Например:
00000111 >> 2 = 00000001
Оператор << работает аналогично, но "вдвигает" нули справа, при этом количество байт, отводящиеся под это число может увеличиться.
UPD: я вот прочитал это и не знаю под каким веществом писал эту статью. Во флеше для целых чисел есть только int и uint, причем они оба всегда по 4 байта (32 бита). Что я имел ввиду под количеством "новых" байт останется для меня загадкой.
Например:
11000000 << 2 = 00000011 00000000
Мы подвинули число 2 раза влево, вдвинув нули справа, но из за этого число не смогло поместиться в 8 бит и ему был отведен ещё один байт. Максимальное число, которое можно "всунуть" байт - это 255 (11111111).
А вот теперь самое вкусное. Как это можно применить на практике?
Но сперва поправка: всё нижеописанное будет иметь место при работе с целыми положительными числами.
Наверное, вы уже задумались над тем, что если бит - это 0 или 1, то есть false или true - то можно передать 8 логических значений за один байт! Остался вопрос, как это сделать приятным для использования. А очень просто: у нас есть оператор |. И если мы заведем константы с числами "00000001", "00000010", "00000100" и т.д., то объединяя их мы можем получать комбинации для передачи этих самых 8 значений одним числом.
В итоге каждое число, от 0 до 255 будет занято какой-то одной уникальной комбинацией этих 8 флагов.
Ради примера сделаем что нужно передать 3 логических значения: горит ли первая лампочка, вторая и третья. Каждая из них может гореть вне зависимости, горит ли какая-то другая.
Сейчас для краткости записи я буду писать только первые 3 разряда, т.е. число 00000111 я буду записывать как 111.
Что делаем:
Заводим константы со значениями 001, 010, 100. В AS3 убрали возможность записывать число в двоичном виде, поэтому используя калкулятор записываем их в десятичном:
Код:
const b1 = 1 const b2 = 2 const b3 = 4
Теперь, чтобы задать что горит, например, первая лампочка и третья нужно передать число 101. Его можно записать вот так: "100 | 001", то есть, проще говоря b1 | b3.
Вот и всё. Мы передали уникальное число, которое соответствует комбинации первой и третьей лампочки включенной, и выключенной второй. Осталось это дело только распознать там, куда мы это передали.
А распознаётся это дело с помощью оператора &. Это битовая маска, так сказать.
Наложить её очень просто: нужно просто понять, где нам нужно видеть биты, а где не нужно. Например, чтобы понять, включена ли первая лампочка нужно наложить на это переданное число маску "001" и посмотреть на результат. Второй и третий бит будут равны 0, т.к. оператор "и" их не пропустит, а результат от первой лампочки будет целиком зависеть от переданного числа: если первый бит у него 1 - результатом будет 1, если второй бит 0, то результатом будет 0.
Со второй лампочкой аналогично, накладывая маску 010 - если лампочка не горит, значит результат будет 0, если горит - результат будет 010. Почему 010, а не 001? Потому что маска "не убивает" разряды, она просто маскирует. Если этот момент вам неясен, тогда взгляните сюда:
111 & 010 = 010
1 & 0 = 0
1 & 1 = 1
1 & 0 = 0
Разряды не пропали.
Но вспомним: 010 у нас лежит в константе b2, а 001 в константе b1. В итоге, достаточно проверить (number & b1) > 0 чтобы понять, горит ли первая лампочка, (number & b2) > 0 чтобы понять, горит ли вторая. Заметьте, используя константы совсем нету необходимости помнить что какое число означает и делать какие-то сложные операции.
К слову, такой механизм реализован в Array.sort.
Теперь практическое применение сдвигов: предположим, у вас есть какой нибудь цвет, например 0x45FF15 и вам нужно узнать каждую его составляющую - красный, зеленый и синий. Зная битовые операции, нет ничего проще!
Начнем с синего. Синий цвет - это последние две цифры, нужно замаскировать остальные и оставить просто их. Да-да, замаскировать - значит наложить маску, осталось понять какую. А очень простую - 0x0000FF. Число 0x45FF15 - это 3 байта: третий байт 0x45, второй 0xFF, первый байт 0x15. 0x - это префикс, означающий что дальше пойдёт число в шестнадцатеричном исчислении.
Значит, нам нужно сделать маску, чтобы второй и третий байты были нулями, а всё в первом байте было единицами. Это позволит нам "убить" второй и третий байты, а первый байт мы оставим в первозданном виде. Значит, второй и третий байт должны быть по 0x00, а третий байт должен быть заполнен полностью единицами, то есть нести число 255 или 0xFF - это десятичная и шестнадцатеричная запись одного и того же числа, о чем рассказывалось ранее. Отсюда и получаем: 0x0000FF.
(0x45FF15 & 0x0000FF) = 0x000015
Нули справа незначимы - поэтому результат и есть 0x15 - то что нам нужно!
Теперь нужно взять второй байт - зеленый. Мыслим логически: сперва нужно спрятать все байты кроме второго. Аналогично
(0x45FF15 & 0x00FF00) = 0x00FF00
Хм, получилось, но не совсем... Вот бы сдвинуть FF вправо. Слово сдвинуть не о чем не напоминает? Именно, о сдвигах. Но на сколько сдвинуть? Сдвигаем мы на столько, чтобы правый байт ушел в небытие. А в байте 8 битов. Значит, нужно сдвинуть на 8!
0x00FF00 >> 8 = 0x0000FF = 0xFF
Получили заветное число. Как получить красный? "Наверное, нужно наложить маску на красный, а потом сдвинуть!" - скажете вы. Но всё ещё проще: правые байты уйдут вправо в небытие, оставив только третий байт, а слева вдвинуться нули - поэтому достаточно просто сдвинуть это дело на 16, т.е. на 2 байта:
(0x45FF15 >> 16) = 0x000045 = 0x45
В конце хочу сказать, что флеш предоставляет отличный метод у всех числовых типов: toString, который имеет аргумент radix. Он позволяет выводить число в любой системе исчисления. Например, чтобы увидеть результат (16 << 3) в двоичной системе достаточно ввести
А чтобы увидеть число 255 в шестандцатеричной:
(Скобки написаны ради того, чтобы компилятор воспринимал точку как оператор, а не как разделитель между дробной и целой частью).
Следует заметить, что в статье рассмотрены не все бинарные операторы.
Если тема вам интересна - предлагаю продолжить самообучение, например, отсюда.
Таким образом биты - это совсем не страшно, а вполне хороший инструмент для работы. Но и как любой инструмент его нужно применять там, где нужно. Например, пример с 8 флагами очень хорош для передачи по интернет-соединению, такому как сокет - вы будете экономить трафик сервера и пользователя, но не нужно ради какого-нибудь класса всего с 2 флагами вводить подобный трюк. Трюков с битами достаточно много - например ранее я рассказывал об умножении и делении на 2 путём сдвига, битовыми операциями можно делить с остатком и ещё много чего. Просто ищите, экспериментируйте и делитесь своим опытом с коллегами.
Всего комментариев 72
Комментарии
![]() ![]() |
|
Молодец, хорошая статья, и полезная. Жду не дождусь когда про байткод начнешь писать
![]() |
![]() ![]() |
|
Еще советую посмотреть книгу "What can you do with bytes?" от Thibault Imbert. Книга написана на простом английском.
|
![]() ![]() |
|
это не логические операторы а бинарные. логические это && и ||
и почему так выборочно выбраны операторы? где ~, где ^, где >>> ? |
![]() ![]() |
|
Тигер, с вычленением каналов цвета не совсем правильно поступаешь, а именно с красным.
Ты просто сдвигаешь на 16 бит вправо число и получаешь "красный". Все хорошо, пока слева от канала красного стоят нули. Если там будет что-то другое, то сам понимашь, охват "красного" слегка расширится ![]() Вообще принято извлекать и объединять каналы цвета так: Код:
A = colorARGB >> 24 & 0xFF, R = colorARGB >> 16 & 0xFF, G = colorARGB >> 8 & 0xFF, B = colorARGB & 0xFF; colorARGB = A << 24 | R << 16 | G << 8 | B; |
|
Обновил(-а) i.o. 30.08.2010 в 19:41
|
![]() ![]() |
|
Цитата:
и почему так выборочно выбраны операторы?
Однако, если с положительными числами все просто, то с отрицательными есть некоторые нюансы, которые хотелось бы прояснить. Когда-то я копался в битовых операциях и все было ясно и понятно. А потом прочитал в хелпе, что "-1" в двоичном виде выглядит как "11111111111111111111111111111111 (32 единицы)". Аналогично и другие числа. И вот тут я впал в ступор. Откуда виртуальная машина знает, что "11111111111111111111111111111111" - это "-1" в двоином виде, а не скажем uint.MAX_VALUE ? Сперва проверяется uint/int ? Не вопрос... Откуда машина знает, что это, скажем, не есть отрицательное "15", представление которого в одном байте "00001111" ? Локика такая (судя по хелпу): *) "двоичное представление" - "десятичное представление" 1) "11111111111111111111111111111111" - "-1" 2) "11111111111111111111111111111000" - "-8" 3) ????? - "-15" Логично было бы увидеть так: *) "двоичное представление" - "десятичное представление" 1) "10000000000000000000000000000001" - "-1" 2) "10000000000000000000000000001000" - "-8" 3) "10000000000000000000000000001111" - "-15" где крайний левый разряд отвечает за знак. Но имеем то, что имеем. Просьба знающим людям разъяснить. toAS3Coder: ================== "Enter the password: ... Книга написана на простом английском. Checking... Access Granted!" ================== забираю, спасибо |
|
Обновил(-а) switcher! 30.08.2010 в 18:07
|
![]() ![]() |
|
чего в var напишите так он и распознает.
|
![]() ![]() |
|
@BlooD: я не о сдвигах, и даже не конкретно об этой теме, а вообще. Ну, даже на текущем примере - откуда ты узнал, что флеш берёт информацию о числе из типа?
Сам догадался из тестов и изучения нужной тебе темы или есть какие-то супер-секретные статьи, к которым допускаются только избранные? ![]() incvizitor, тут уже я рискну ответить. В байткоде: Код:
// var a:int=10; pushbyte 10 convert_i setlocal1 /* a */ // var b:uint = 10; pushbyte 10 convert_u setlocal2 |
|
Обновил(-а) Psycho Tiger 30.08.2010 в 20:30
|
![]() ![]() |
|
AS3Coder, спасибо за ссылку, но это убило)
Цитата:
• 1 kilo-byte (kb) = 10³ bytes (1 000 bytes) ;
• 1 mega-byte (mb) = 106 bytes = 1 000 kb (1 000 000 bytes). |
![]() ![]() |
|
Очень интересная статья.
Интересно, решен ли вопрос о переносе влево через флаг C? |
![]() ![]() |
|
Знак - это один старший бит. Только контекст типа определяет то, как этим знаком воспользоваться - считать его как sign или присудить к разрядности.
|
![]() ![]() |
|
я в курсе, спасибо. Мы немного о другом говорим -)
|
![]() ![]() |
|
Я рад, что мы говорим об одном и том же.
Цитата:
обычно при операции с битами знак становится не важен.
то есть, если вы начали оперировать битами, то нафига вам знак? |
|
Обновил(-а) dimarik 31.08.2010 в 00:18
|
![]() ![]() |
|
Цитата:
Я так и не смог найти этому делу практическое применение.
|
![]() ![]() |
|
а если uint? тоже вроде 32 и там речи про знак нет.
Имеются ввиду именно операции с битами. Я дополнил предыдущий комментарий. |
![]() ![]() |
|
Тигр, а что понимается под "Артефактом"? Вылезшие за границы разряды ?
|
|
Обновил(-а) switcher! 31.08.2010 в 01:10
|
![]() ![]() |
|
Не совсем. >> только смотрит крайний левый бит 0 или 1 и вставляет 0 или 1 соответственно.
>> вообще не в курсе, что есть какие то там знаки. Знаками занимается int и для него (это ты и так наверняка знаешь, просто еще раз объясню) все что <= 0x7FFFFFFF (+2147483647 для int) является положительным, а все что >= 0x80000000 (-2147483648 для int) - отрицательным. Это, кстати, как раз отвечает на вопрос switcher!. Вот тем и удобно. Гипотетически представь, что мы оперируем даже не с int32, а с int5. И решили мы разделить его так: положительные от 00000(+0) до 01111(+15). Общепринято в AS3 также. отрицательные от 10000(-0) до 11111(-15). Общепринято в AS3: 10000(-16) до 11111(-1). А теперь нам нужно скажем из 00001 вычесть 00010 (т.е 1 - 2 = -1) В результате вычитания мы получим 11111. Таким образом при переводе в наш int5 мы получим -15 0_o. А при переводе в общепринятый int5 -1. Т.е с нашим int5 нам понадобятся еще дополнительные усилия, чтобы понять к чему этот результат относить, а потом, если мы определили, что это отрицательный, то еще и преобразовывать к соответствующему виду, т.е 11111 превращать в 10001. |
|
Обновил(-а) i.o. 31.08.2010 в 12:45
|
![]() ![]() |
|
Да, я примерно это и сказал, только плохо мысль выразил. Ещё раз спасибо)
|
![]() ![]() |
|
Люди... о чем вы?
В as3 представление отрицательного числа в битах (Integer) происходит посредством дополнительного кода. Люди, знающие и понимающие, не могли не заметить, что упор в своих рассуждениях я делал на прямой код, хотя изначально шел в своих рассуждениях по неверной дорожке. Это я и хотел услышать с самого начала, но на тот момент не владел понятиями. КАК к этому относится разбиение 4-х байт на байты? При любом из способов представления числа знаковый бит все тот же - крайний левый. И хотите вы того или нет, при любом отрицательном числе с ним придется считаться. КАК к этому относится однородность представления положительных чисел в uint и int, когда я изначально спрашивал про отрицательные числа? Цитата:
положительные от 0000(+0) до 01111(+15). Общепринято также.
отрицательные от 1000(-0) до 11111(-15). Общепринято: 1000(-16) до 11111(-1). Соответственно, нет отрицательного нуля и число «-15» (если сузить до 5-ти бит) представлено как 10001. Я не придираюсь и никого не ругаю. Я говорю то, что вижу. Возможно резкость моя не оправдана - прошу простить. Но иного мнения у меня пока нет. |
![]() ![]() |
|
"Общепринято" - имелось ввиду "принято в AS3", т.е дополнительный код.
Цитата:
Вы говорите о прямом коде. И при этом совершенно не замечаете, что int.MAX_VALUE и int.MIN_VALUE по модулю разнятся на единицу, что при прямом коде невозможно.
Цитата:
int.MAX_VALUE и int.MIN_VALUE по модулю разнятся на единицу
Цитата:
Соответственно, нет отрицательного нуля и число «-15» (если сузить до 5-ти бит) представлено как 10001.
Цитата:
число «-15» (если сузить до 5-ти бит) представлено как 10001
|
|
Обновил(-а) i.o. 31.08.2010 в 12:39
|
![]() ![]() |
|
Цитата:
"Общепринято" - имелось ввиду "принято в AS3", т.е дополнительный код.
Цитата:
и при этом удивляетесь
Цитата:
switcher!
при прямом коде невозможно. i.o. при прямом такое как раз невозможно Цитата:
в первой же таблице идет речь об отрицательном нуле при прямом коде.
|
|
Обновил(-а) switcher! 31.08.2010 в 12:43
|
![]() ![]() |
|
Цитата:
Цитата:
Цитата:
положительные от 00000(+0) до 01111(+15). Общепринято также.
отрицательные от 10000(-0) до 11111(-15). Общепринято: 10000(-16) до 11111(-1). После этого говорите: Цитата:
И при этом совершенно не замечаете, что int.MAX_VALUE и int.MIN_VALUE по модулю разнятся на единицу, что при прямом коде невозможно
Цитата:
положительные от 00000(+0) до 01111(+15)
отрицательные от 10000(-0) до 11111(-15) Цитата:
И при этом совершенно не замечаете
|
|
Обновил(-а) i.o. 31.08.2010 в 13:00
|
![]() ![]() |
|
Насколько помню, так Number всегда был double (число двойной точности, мантисса 52 бита).
|
![]() ![]() |
|
Цитата:
Прямой код — способ представления двоичных чисел с фиксированной запятой в компьютерной арифметике
|
![]() ![]() |
|
Резюме. Операций с битами в AS нет. Только эрзац.
|
![]() ![]() |
|
<<< - что за звеР такой?
|
![]() ![]() |
|
Здесь описаны сдвиги.
Есть последовательность битов, например, в формате SWF файла. Именно битов! Так задан RECT, MATRIX и некоторые другие теги. Эта последовательность имеет переменную длину. Чтобы работать с этими числами необходимо сделать преобразование. Очень удобно работать именно с битами через непосредственно сдвиги в другое хранилище (переменную). Такого инструмента AS не предоставляет. Т.е. нет у нас foo = 0000 0100; bar = 0000 0001; RR(foo, bar, 2); trace(foo, bar) // 0100 0001b, 0000 0000b Низкоуровневые команды процессора позволяют в операциях сдвига через CF как левый так и правый сдвиг. Здесь CF выполняет не совсем свою "обязанность". Вот команды вращения регистров Z80. ![]() Где t - название регистра. Для нас равносильно названию переменной (типа int, uint) |
|
Обновил(-а) dimarik 04.09.2010 в 01:23
|
![]() ![]() |
|
![]() ![]() |
|
Цитаты из вики, но в своё время кодил на масме очень плотно (сейчас аж жуть берёт, как вспомню)
нафик-нафик.. |
![]() ![]() |
|
А мне нравилось. Я battle city пытался портировать на zx-spectrum когда учился в институте. Кодил в Zeus. Это такое IDE, как сказали бы сейчас )
Аха. Я такой старый. В мои времена был Поиск, БК, ДВК, Вектор-06ц, Корвет, Орион и MSX-2 (до сих пор имею на ноуте эмулятор этого шедевра). |
|
Обновил(-а) dimarik 04.09.2010 в 01:47
|
Последние записи от Psycho Tiger
- Тонкости и трюки ActionScript`а, которые... бесполезны (10.05.2011)
- Vkontakte: как пользоваться wall.post, нужен ли теперь wall.savePost? (05.03.2011)
- А пятый контер-страйк хорош. (19.01.2011)
- Пацаны, гоу Вконтакте? (21.12.2010)
- Давайте начистоту (18.12.2010)