Во-первых, замечание об исходной проблеме и последующих расширениях, которые вы упомянули:
Операция "перемещение бита", которую вы описываете, на самом деле является вращением непрерывного диапазона битов. В вашем примере вы вращаете биты 1-5 включительно, на один бит влево:
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
| 0 | 1 | 0<--1<--1<--0<--1 | 0 | -> | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
+---+---+-|-+---+---+---+-^-+---+ +---+---+---+---+---+---+---+---+
| |
+---------------+
Если вы считаете, что более общей формой этой операции является «поворот диапазона битов, оставленных на некоторое количество» с тремя параметрами:
- младший значащий бит для включения в ротацию
- самый значащий бит для включения в ротацию
- количество бит для поворота на
затем он становится единым базовым примитивом, который может выполнять все того, что вы хотите сделать:
- очевидно, что вы можете перемещать любой бит (выберите соответствующие наименее / наиболее значимые параметры битов);
- вы можете вращать влево или вправо, потому что если вы вращаете диапазон n битов, то вращение вправо на k битов - то же самое, что вращение влево на n - k бит;
- это тривиально обобщается на любую битовую ширину;
- по определению мы можем вращаться более чем на один бит за раз.
Итак, теперь все, что нужно, это построить этот примитив ...
Для начала нам почти наверняка понадобится битовая маска для битов, которые нас интересуют.
Мы можем сформировать маску для битов 0 - n , сдвинув 1 на n + 1 бит влево, а затем вычтя 1. Например, маска для битов 0-5 будет (в двоичном виде):
00111111
... который можно сформировать, взяв 1:
00000001
... сдвиг 5 + 1 = 6 бит влево:
01000000
... и вычитая 1, получим:
00111111
В C это будет (1 << (bit + 1)) - 1
. Но здесь есть тонкость, по крайней мере для C (и я прошу прощения за отступление, когда вы пометили это как независимое от языка, но это важно, и, вероятно, есть и похожие проблемы и в других языках): сдвиг на Ширина вашего типа (или больше) приводит к неопределенному поведению. Поэтому, если бы мы пытались создать маску для битов 0-7 для 8-битного типа, вычисление было бы (1 << 8) - 1
, что было бы неопределенным. (Это может работать в некоторых системах и некоторых компиляторах, но не может быть переносимым.) Существуют также неопределенные проблемы поведения со знаковыми типами в случае, если вы в конечном итоге перейдете на бит знака.
К счастью, в C мы можем избежать этих проблем, используя тип unsigned
и записывая выражение как (1 << bit) + (1 << bit) - 1
. Арифметика со значениями без знака n -бит определяется стандартом для уменьшения по модулю 2 n , и все отдельные операции четко определены, поэтому мы гарантированно получим правильный ответ.
(Конец отступления.)
ОК, теперь у нас есть маска для битов 0 - msb . Мы хотим сделать маску для битов lsb - msb , что мы можем сделать, вычтя маску для битов 0 - ( lsb -1), что (1 << lsb) - 1
. например,
00111111 mask for bits 0-5: (1 << 5) + (1 << 5) - 1
- 00000001 mask for bits 0-0: (1 << 1) - 1
-------- -------------------------------
00111110 mask for bits 1-5: (1 << 5) + (1 << 5) - (1 << 1)
Итак, окончательное выражение для маски:
mask = (1 << msb) + (1 << msb) - (1 << lsb);
Биты, которые должны быть повернуты, могут быть выбраны побитовым И с маской:
to_rotate = value & mask;
... и биты, которые останутся нетронутыми, можно выбрать с помощью И с инвертированной маской:
untouched = value & ~mask;
Само вращение можно легко выполнить из двух частей: во-первых, мы можем получить самые левые биты вращаемой части, просто повернув to_rotate
влево и отбросив любые биты, которые выходят за пределы маски:
left = (to_rotate << shift) & mask;
Чтобы получить самые правые биты, поверните to_rotate
вправо на ( n - shift ) бит, где n - это количество бит, которые мы вращаем (это n может быть вычислено как msb + 1 - lsb
):
right = (to_rotate >> (msb + 1 - lsb - shift)) & mask;
Окончательный результат может быть получен путем объединения всех битов из untouched
, left
и right
:
result = untouched | left | right;
Ваш оригинальный пример будет работать следующим образом (msb
равно 5, lsb
равно 1 и shift
равно 1):
value = 01011010
mask = 00111110 from (1 << 5) + (1 << 5) - (1 << 1)
01011010 value
& 00111110 mask
----------
to_rotate = 00011010
01011010 value
& 11000001 ~mask (i.e. inverted mask)
----------
untouched = 01000000
00110100 to_rotate << 1
& 00111110 mask
----------
left = 00110100
00000001 to_rotate >> 4 (5 + 1 - 1 - 1 = 4)
& 00111110 mask
----------
right = 00000000
01000000 untouched
00110100 left
| 00000000 right
----------
result = 01110100
Вот другой пример с 16-битным входным значением, msb
= 15, lsb
= 4 и shift
= 4 (который вращает 3 верхние шестнадцатеричные цифры четырехзначного шестнадцатеричного значения):
value = 0101011001111000 (0x5678)
mask = 1111111111110000 from (1 << 15) + (1 << 15) - (1 << 4)
0101011001111000 value
& 1111111111110000 mask
------------------
to_rotate = 0101011001110000
0101011001111000 value
& 0000000000001111 ~mask
------------------
untouched = 0000000000001000
0110011100000000 to_rotate << 4
& 1111111111110000 mask
------------------
left = 0110011100000000
0000000001010110 to_rotate >> 8 (15 + 1 - 4 - 4 = 8)
& 1111111111110000 mask
------------------
right = 0000000001010000
0000000000001000 untouched
0110011100000000 left
| 0000000001010000 right
------------------
result = 0110011101011000 = 0x6758