1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9

# конспект · шаг 9 из 9

Конспект: маски, сдвиги и множество на битах

выжимка - её можно унести в заметки

Понятия

Бит - один двоичный разряд. Бит номер k весит 2ᵏ, счёт справа с нуля.

Маска - число-трафарет: единицы в нём отмечают, какие разряды другого числа нужны.

Слово - n битов фиксированной ширины; всё, что не поместилось, отбрасывается. Арифметика в слове идёт по модулю 2ⁿ.

Дополнительный код - чтение слова со знаком: старший бит единица → значение u − 2ⁿ. Запись -x - это 2ⁿ − x, она же ~x + 1.

Подмаска маски m - число s с s & m == s: запись подмножества.

Расстояние Хэмминга - число позиций, в которых две строки равной длины различаются.

Что унести

ШЕСТЬ ОПЕРАЦИЙ                 12 = 1100, 10 = 1010
a & b    единица у обоих       1000 = 8
a | b    хотя бы у одного      1110 = 14
a ^ b    биты разные           0110 = 6
~a       переворот             −13          ~x = −x − 1
a << k   умножить на 2ᵏ        12 << 2 = 48
a >> k   разделить на 2ᵏ вниз  12 >> 2 = 3,  −7 >> 1 = −4

ПОРЯДОК ДЕЙСТВИЙ, от слабого к сильному
сравнения < | < ^ < & < сдвиги < + − < * / // % < ~ и унарный минус < **
1 << n + 1  это  1 << (n + 1)          x & 1 == 0  в Python это  (x & 1) == 0,  в C -  x & (1 == 0)
ТРИ ДВИЖЕНИЯ
достать бит k           x >> k & 1
вырезать поле           x >> сдвиг & маска           0x2E8B57 >> 8 & 0xFF = 0x8B
склеить из полей        a << 16 | b << 8 | c
маска из k единиц       (1 << k) - 1
сеть и узел             адрес & маска,  адрес & ~маска & 0xFFFFFFFF
СЛОВО ИЗ 8 БИТОВ          без знака     со знаком
0111 1111                    127           127
1000 0000                    128          −128
1111 1011                    251            −5
1111 1111                    255            −1

в Python:  слово = x & 0xFF         со знаком = (u ^ 0x80) - 0x80
100 + 100 → −56      127 + 1 → −128      −(−128) → −128
расширение знака: слева копии старшего бита
сдвиг вправо: арифметический копирует знак (−8 → −4), логический вдвигает ноль (248 → 124)
МНОЖЕСТВО НОМЕРОВ 0 … n−1 КАК ЧИСЛО
пересечение  a & b        объединение  a | b       разность  a & ~b       ровно в одном  a ^ b
входит ли k  a >> k & 1   добавить  a | 1 << k     убрать  a & ~(1 << k)  переключить  a ^ 1 << k
a внутри b   a & b == a   размер  a.bit_count()    всё множество  (1 << n) - 1
дополнение   ~a & vse

два набора по 25 чисел: поиск по спискам 591 шаг, готовые маски - 1 шаг
ПОДМАСКИ                                 m = 1101:  1101 1100 1001 1000 0101 0100 0001 0000
s = m
пока s > 0:                              s − 1: младшая единица гаснет, нули под ней зажигаются
    ...                                  & m: стирает то, что зажглось не на своём месте
    s = (s − 1) & m                      s & (s − 1): s без младшей единицы
отдельно: s = 0

подмасок у m: 2 в степени «число единиц»       пар (m, s) на n битах: 3ⁿ, не 4ⁿ
n = 8: 6561 пара, 19 941 шаг против 137 891       потолок: 15-18 элементов
приём битового параллелизма было стало
расстояние Хэмминга: (a ^ b).bit_count() 96 021 шаг 2 470
достижимые суммы: mozhno \| mozhno << w 400 13
достижимость в графе: stroka[i] \| stroka[k] 50 827 3 712

Выигрыш - ширина слова, 64 раза. Порядок роста тот же: строка длиннее слова - массив слов.

Зета и Мёбиус: суммы по подмаскам и их обращение - O(n·2ⁿ). Свёртка по непересекающимся частям через слои мощности - O(n²·2ⁿ) действий и O(n·2ⁿ) памяти.

Ловушки джокера

Скобки. Сдвиг слабее сложения: 1 << k - 1 - это 1 << (k − 1).

^ как степень. 2 ^ 10 равно 8. Степень - 2 ** 10 или 1 << 10.

+ вместо |. Работает, пока биты не пересеклись; потом перенос портит соседний разряд: {1, 3} плюс элемент 1 даёт {2, 3}.

~ без рамки. ~0x0F в Python равно −16. Нужно ~0x0F & 0xFF.

Переполнение молчит. Сумма двух положительных стала отрицательной. В C для знаковых это ещё и неопределённое поведение.

Знаковое с беззнаковым. В C -1 < 1u ложно, а for (unsigned i = n - 1; i >= 0; i--) бесконечен.

Модуль наименьшего числа. −(−128) в восьми битах равно −128.

Сдвиг вместо деления отрицательного. -7 >> 1 равно −4, деление в C даёт −3.

Номера с единицы. Цифре d отводят бит d − 1 и забывают сдвиг при записи или при чтении.

Проверка бита через == 1. a & m равно не единице, а m. Сравнивать с нулём.

s >= 0 в цикле подмасок. (0 − 1) & m возвращает m, цикл бесконечен.

Одно разбиение много раз. Три группы цикл находит шестью способами; лечится правилом «старший элемент обязан входить в s».

Число со сдвигом растёт. mozhno << w без маски удлиняется в Python и теряет биты в C.

Первый символ строки - старший бит. После int(s, 2) символ номер k слева - бит длина − 1 − k.

Куда это ведёт

В цикле подмасок спрятаны два наблюдения про вычитание единицы: s − 1 переворачивает младшую единицу и всё под ней, а s & (s − 1) эту единицу убирает. Следующая глава строит на них целое семейство приёмов: проверка степени двойки одной строкой, счёт единиц за число единиц, а не за число битов, и логарифм числа без цикла.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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