Как сдвинуть массив байтов на 12 бит
Вопрос
Я хочу сдвинуть содержимое массива байтов на 12 бит влево.
Например, начиная с этого массива типа uint8_t shift[10]
:
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xBC}
Я хотел бы сдвинуть его влево на 12 бит, в результате чего:
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xC0, 0x00}
Решение
Ура указателям!
Этот код работает, просматривая каждый байт на 12 бит вперед и копируя нужные биты вперед.12 бит — это нижняя половина (байт) следующего байта и верхняя половина двух байтов.
unsigned char length = 10;
unsigned char data[10] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0A,0xBC};
unsigned char *shift = data;
while (shift < data+(length-2)) {
*shift = (*(shift+1)&0x0F)<<4 | (*(shift+2)&0xF0)>>4;
shift++;
}
*(data+length-2) = (*(data+length-1)&0x0F)<<4;
*(data+length-1) = 0x00;
Джастин написал:
@Майк, твое решение работает, но не работает.
Ну, я бы сказал, что обычная операция сдвига делает именно это (называемое переполнением) и просто позволяет дополнительным битам выпадать вправо или влево.Если хотите, его достаточно просто носить с собой — просто сохраните 12 бит, прежде чем начать сдвиг.Может быть, вам нужен круговой сдвиг, чтобы вернуть переполненные биты вниз?Может быть, вы хотите перераспределить массив и увеличить его?Вернуть переполнение вызывающему абоненту?Вернуть логическое значение, если ненулевые данные были переполнены?Вам придется определить, что для вас означает перенос.
unsigned char overflow[2];
*overflow = (*data&0xF0)>>4;
*(overflow+1) = (*data&0x0F)<<4 | (*(data+1)&0xF0)>>4;
while (shift < data+(length-2)) {
/* normal shifting */
}
/* now would be the time to copy it back if you want to carry it somewhere */
*(data+length-2) = (*(data+length-1)&0x0F)<<4 | (*(overflow)&0x0F);
*(data+length-1) = *(overflow+1);
/* You could return a 16-bit carry int,
* but endian-ness makes that look weird
* if you care about the physical layout */
unsigned short carry = *(overflow+1)<<8 | *overflow;
Другие советы
Вот мое решение, но, что еще важнее, мой подход к решению проблемы.
Я подошел к проблеме
- рисование ячеек памяти и рисование стрелок от пункта назначения к источнику.
- составил таблицу, показывающую приведенный выше рисунок.
- маркировка каждой строки таблицы относительным адресом байта.
Это показало мне шаблон:
- позволять
iL
быть младшим полубайтом (полубайта)a[i]
- позволять
iH
быть главным грызуномa[i]
iH = (i+1)L
iL = (i+2)H
Этот шаблон справедлив для всех байтов.
В переводе на C это означает:
a[i] = (iH << 4) OR iL
a[i] = ((a[i+1] & 0x0f) << 4) | ((a[i+2] & 0xf0) >> 4)
Теперь мы сделаем еще три наблюдения:
- поскольку мы выполняем присваивания слева направо, нам не нужно хранить какие-либо значения во временных переменных.
- у нас будет специальный случай для хвоста:все
12 bits
в конце будет ноль. - мы должны избегать чтения неопределенной памяти за пределами массива.поскольку мы никогда не читаем больше, чем
a[i+2]
, это влияет только на последние два байта
Итак, мы
- обрабатывать общий случай, используя цикл for
N-2 bytes
и выполнив общий расчет выше - обработать предпоследний байт, установив
iH = (i+1)L
- обработать последний байт, установив для него значение
0
данный a
с длиной N
, мы получаем:
for (i = 0; i < N - 2; ++i) {
a[i] = ((a[i+1] & 0x0f) << 4) | ((a[i+2] & 0xf0) >> 4);
}
a[N-2] = (a[N-1) & 0x0f) << 4;
a[N-1] = 0;
И вот оно...массив сдвинут влево на 12 bits
.Это можно легко обобщить до смещения N bits
, отметив, что будет M
операторы присваивания, где M = number of bits modulo 8
, Я считаю.
На некоторых машинах цикл можно сделать более эффективным, переведя его на указатели.
for (p = a, p2=a+N-2; p != p2; ++p) {
*p = ((*(p+1) & 0x0f) << 4) | (((*(p+2) & 0xf0) >> 4);
}
и используя самый большой целочисленный тип данных, поддерживаемый ЦП.
(Я только что напечатал это, так что сейчас самое время кому-нибудь просмотреть код, тем более, что при перестановке битов очень легко ошибиться.)
Давайте сделаем это лучшим способом переключения N
бит в массиве 8-битных целых чисел.
N - Total number of bits to shift
F = (N / 8) - Full 8 bit integers shifted
R = (N % 8) - Remaining bits that need to be shifted
Я предполагаю, что отсюда вам придется найти наиболее оптимальный способ использования этих данных для перемещения целых чисел в массиве.Общие алгоритмы заключаются в применении полных целочисленных сдвигов, начиная с правой части массива и перемещая каждое целое число. F
индексы.Ноль заполняет новые пустые места.Затем, наконец, выполните R
битовый сдвиг по всем индексам, опять же начиная справа.
В случае сдвига 0xBC
к R
бит, вы можете вычислить переполнение, выполнив побитовое И, и сдвиг, используя оператор битового сдвига:
// 0xAB shifted 4 bits is:
(0xAB & 0x0F) >> 4 // is the overflow (0x0A)
0xAB << 4 // is the shifted value (0xB0)
Имейте в виду, что 4 бита — это просто маска:0x0F или просто 0b00001111.Это легко вычислить, динамически построить или даже использовать простую статическую таблицу поиска.
Я надеюсь, что это достаточно общее.Я совсем не силен в C/C++, так что, возможно, кто-нибудь сможет почистить мой синтаксис или уточнить детали.
Бонус:Если вы умеете обращаться с C, вы можете объединить несколько индексов массива в одно 16-, 32- или даже 64-битное целое число и выполнить сдвиги.Но это, вероятно, не очень портативно, и я бы не рекомендовал этого делать.Просто возможная оптимизация.
Вот рабочее решение с использованием временных переменных:
void shift_4bits_left(uint8_t* array, uint16_t size)
{
int i;
uint8_t shifted = 0x00;
uint8_t overflow = (0xF0 & array[0]) >> 4;
for (i = (size - 1); i >= 0; i--)
{
shifted = (array[i] << 4) | overflow;
overflow = (0xF0 & array[i]) >> 4;
array[i] = shifted;
}
}
Вызовите эту функцию 3 раза для 12-битного сдвига.
Решение Майка может быть быстрее из-за использования временных переменных.
32-битная версия...:-) Обрабатывает 1 <= count <= num_words
#include <stdio.h>
unsigned int array[] = {0x12345678,0x9abcdef0,0x12345678,0x9abcdef0,0x66666666};
int main(void) {
int count;
unsigned int *from, *to;
from = &array[0];
to = &array[0];
count = 5;
while (count-- > 1) {
*to++ = (*from<<12) | ((*++from>>20)&0xfff);
};
*to = (*from<<12);
printf("%x\n", array[0]);
printf("%x\n", array[1]);
printf("%x\n", array[2]);
printf("%x\n", array[3]);
printf("%x\n", array[4]);
return 0;
}
@Джозеф, обратите внимание, что переменные имеют ширину 8 бит, а ширина сдвига — 12 бит.Ваше решение работает только для размера N <= переменной.
Если вы можете предположить, что ваш массив кратен 4, вы можете преобразовать его в массив uint64_t и затем работать над этим.Если оно не кратно 4, вы можете работать с 64-битными фрагментами столько, сколько сможете, а с остатком работать один за другим.Это может быть немного больше кодирования, но я думаю, что в конечном итоге это более элегантно.
Есть несколько крайних случаев, которые делают эту проблему аккуратной:
- входной массив может быть пустым
- последний и предпоследний биты необходимо обрабатывать особым образом, поскольку в них сдвинуты нулевые биты
Вот простое решение, которое перебирает массив, копируя младший полубайт следующего байта в его старший полубайт, а старший полубайт следующего (+2) байта в его младший полубайт.Чтобы избежать повторного разыменования указателя просмотра вперед, он поддерживает двухэлементный буфер с «последним» и «следующим» байтами:
void shl12(uint8_t *v, size_t length) {
if (length == 0) {
return; // nothing to do
}
if (length > 1) {
uint8_t last_byte, next_byte;
next_byte = *(v + 1);
for (size_t i = 0; i + 2 < length; i++, v++) {
last_byte = next_byte;
next_byte = *(v + 2);
*v = ((last_byte & 0x0f) << 4) | (((next_byte) & 0xf0) >> 4);
}
// the next-to-last byte is half-empty
*(v++) = (next_byte & 0x0f) << 4;
}
// the last byte is always empty
*v = 0;
}
Рассмотрим граничные случаи, которые активируют последовательно все больше частей функции:
- Когда
length
равен нулю, выручаем, не трогая память. - Когда
length
равен единице, мы устанавливаем единственный элемент равным нулю. - Когда
length
равно двум, мы устанавливаем старший полубайт первого байта на младший полубайт второго байта (то есть биты 12–16), а второй байт – на ноль.Мы не активируем цикл. - Когда
length
больше двух, мы попадаем в цикл, перетасовывая байты в двухэлементном буфере.
Если вашей целью является эффективность, ответ, вероятно, во многом зависит от архитектуры вашей машины.Обычно вам следует поддерживать двухэлементный буфер, но одновременно обрабатывать машинное слово (32/64-битное целое число без знака).Если вы перемещаете большой объем данных, стоит рассматривать первые несколько байтов как особый случай, чтобы вы могли выровнять указатели машинных слов по словам.Большинство процессоров получают доступ к памяти более эффективно, если доступ осуществляется за пределами границ машинного слова.Конечно, конечные байты также должны обрабатываться особым образом, чтобы вы не трогали память за пределами массива.