Сегодня я представлю концепцию двоичной системы счисления, на которую опираются компьютеры. Обратите внимание, что это ОЧЕНЬ базовое введение в двоичные числа, их назначение и использование, а также операции, которые можно выполнять с ними. До недавнего времени у меня почти не было опыта работы с бинарными файлами, и мои знания все еще в процессе. Манипуляции с битами, такие как битовые векторы и т. д., являются сложными темами, и как младшему инженеру вам нужно будет иметь лишь поверхностное представление о битах и ​​тому подобном. Тем не менее, я призываю вас продолжить изучение тем, которые я представляю сегодня, поскольку понимание того, как работают компьютеры на самом низком уровне, может помочь вам только как программисту. Ладно, это в сторону, давайте взламываем.

Как программист, вы, вероятно, имеете некоторое представление о том, что такое двоичный код, и что все, что делает компьютер, основано на последовательности 1 и 0. Вы можете быть знакомы с 32- и 64-битными операционными системами и, вероятно, знаете, что в байте (обычно) 8 бит, в мегабайте 1 000 000 байт и в гигабайте 1024 мегабайта. Но почему эти ценности такие, какие они есть?

Двоичная система счисления относится к системе счисления с основанием 2. В нашей повседневной жизни мы имеем дело с системой счисления с основанием 10, в которой каждое число от отрицательной бесконечности до бесконечности может быть представлено как последовательность из 10 цифр (0,1,2,3,4,5,6,7,8,9). ), и, читая число слева направо, каждый разряд представляет собой степень числа 10 (разряд 100, разряд 10, разряд 1 и т. д.). Исходя из этого понимания, очень легко перейти к основанию 2, где каждое целое число может быть представлено в виде последовательности из 2 цифр (0 и 1), и если читать слева направо, каждый разряд (или бит) является степенью числа 2 (16-е число). бит, бит 8, бит 4, бит 2 и бит 1, предполагая 5-битное целое число без знака). Сегодня я покажу все примеры, используя только 8-битные целые числа без знака, потому что 8 достаточно для демонстрации того, как работают основные побитовые операции, и потому что использование целых чисел со знаком и отрицательных чисел очень усложняет ситуацию. Тем не менее, как только вы освоитесь с очень простым двоичным кодом, я рекомендую вам изучить представления целых чисел со знаком и отрицательных чисел, и особенно метод их представления с дополнением до двух, который почти повсеместно используется в современных компьютерах и языках программирования.

Итак, перейдем к представлению 8-битных целых чисел без знака в двоичном формате. Это действительно довольно просто, если вы разберетесь с узором. 1 равно 00000001, т. е. бит 1 включен (1 всегда означает «включено», 0 всегда означает «выключено»), а все остальные биты выключены. 2 выглядит как 00000010 (бит 2 включен, все остальные биты выключены). С помощью этого шаблона мы можем представить 27 как 00011011 и 135 как 10000111. Отлично. Также обратите внимание, что из этих примеров легко понять, почему с n битами мы можем представить максимальное целое число без знака 2 ^ n -1, в данном случае 255 (128 + 64 + 32 + 16 + 8 + 4). +2+1).

Также обратите внимание, что при работе с положительными целыми числами без знака очень легко преобразовать двоичное число в число с основанием 10 и наоборот. Реализация javascript для обоих приведена ниже. Этот код значительно усложняется со знаками/при представлении отрицаний.

function convertNumberToBin(num) { 
    return Number(num).toString(2) 
} 
function convertBinaryStringToNumber(bin) { 
    return parseInt(bin, 2) 
}

Хорошо, теперь давайте займемся основными побитовыми операциями. В любом языке программирования существует всего несколько различных операций, которые можно выполнять на битовом уровне: | (или), & (и), ^ (исключающее или), ~ (не), « (сдвиг влево), » (сдвиг вправо) и »› (логический сдвиг вправо). Первые несколько ведут себя почти так же, как при использовании обычных данных. | возвращает 1 (true), если какие-либо переданные ему данные верны. Например 0 | 1 возвращает 1, а 0 | 0 возвращает 0. & возвращает 1 (true), если все переданные ему данные верны. Таким образом, 1 и 1 возвращает 1, 1 и 0 возвращает 0. Xor означает исключающее или, и он работает, выясняя, отличаются ли переданные ему биты. Итак, 1 ^ 1 возвращает 0, 0 ^ 0 возвращает 0, а 1 ^ 0 возвращает 1. ~ работает как оператор взрыва, инвертируя значение бита. ~1 равно 0 и ~0 равно 1.

Как мы вскоре увидим, операторы сдвига используются для многих битовых манипуляций. « немного сдвигает a влево на указанное количество позиций и дополняет 0 справа, чтобы компенсировать потерю. Таким образом, 9 « 2 становится 36 (сдвиг влево 2 эквивалентен умножению на 2 дважды), например, 00001001 ~›

  1. Сдвиг вправо сдвигает бит вправо на указанное количество позиций, а биты, сдвинутые за правый край, отбрасываются. Таким образом, 9 » 2 дает 2 (сдвиг вправо на 2 эквивалентен делению на 2, дважды с последующим округлением до отрицательной бесконечности), например, 00001001 ~> 00000010. Обратите внимание, что это известно как арифметический сдвиг, означающий, что биты сдвигаются оба конца отбрасываются. Существуют также логические сдвиги, при которых отброшенные биты заменяются на 0. Это происходит уже при арифметическом сдвиге влево, не оставляя необходимости в логическом сдвиге влево, но есть потребность в логическом сдвиге вправо. Это не влияет на положительные числа, так что 9 «‹ 2 по-прежнему дает 2, например: 000010001 ~> 00000010. Для отрицательных целых чисел со знаком это не так, но мы не будем сейчас вдаваться в подробности. Просто обратите внимание, что »› лучше подходит для беззнаковых двоичных чисел.

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

function setBit(x, position) { 
    let mask = 1 << position 
    return x | mask 
}

Код работает так. Скажем, x — это двоичная строка, такая как 0000110 (6), и вы хотите установить бит в позиции 5 в значение true. Вы должны передать двоичную строку, равную 5, в качестве позиции (0000101), и, поскольку маска сдвигает 1 в эту позицию, она будет выглядеть как 00100000. Затем X или маска выполняет побитовое сравнение, например так:

0000110 | 0010000

в результате получается 0010110 и успешно устанавливается бит в положение 5.

Очистка установленного бита работает аналогично.

function clearBit(x, position) { 
    let mask = 1 << position 
		return x & ~mask 
}

Маска создается таким же образом, с 1 в той позиции, которой вы хотите управлять, и 0 во всех остальных местах. Затем вы берете ~mask, в которой 0 стоит в той позиции, которой вы хотите управлять, а 1 — везде в остальных местах, и & с x. Поскольку 1 & 0 равно 0, это очищает бит.

Немного перевернуть еще проще, javaScript выглядит так:

function flipBit(x, position) { 
    let mask = 1 << position 
		return x ^ mask 
}

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

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

00001100 (12) + 
00100001 (33) = 
00101101 (45)

Вы просто добавляете каждый столбец. Если бы они оба были 1, вы бы поставили 0 и перенесли 1, как с основанием 10.

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

Сегодняшние мудрые слова немного в другом ключе, и исходят они к нам от легендарного Хантера Томпсона:

«Жизнь не должна быть путешествием в могилу с намерением благополучно прибыть в красивом и хорошо сохранившемся теле, а скорее скользить боком в облаке дыма, полностью измученным, совершенно измученным и громко восклицающим: «Ух ты! Какая поездка!»

До следующего раза!