Алгоритм для копирования N бит в произвольном положении из одного int в другой
Интересная проблема, о которой я размышлял последние несколько дней, - это копирование целочисленных битов в другое целое число в заданной позиции в целевом целое. Так, например, с учетом целевого целого числа 0xdeadbeef
и исходного целого числа 0xabcd
, идея заключалась бы в получении результата 0xabcdbeef
(с заданной позицией 16 бит) или 0xdeabcdef
(с учетом позиции адресата из 8 бит).
При произвольном ограничении избегания условных или петель (позволяя мне использовать только математические/побитовые операции), я разработал следующую функцию (С++)
int setbits(int destination, int source, int at, int numbits)
{
int ones = ((1<<(numbits))-1)<<at;
return (ones|destination)^((~source<<at)&ones);
}
где at
- это место, где исходные биты должны быть скопированы в номер назначения (0-31), а numbits
- количество бит, копируемых из source
(1-32). Насколько я могу судить, этот алгоритм работает для всех значений, за исключением at
= 0 и numbits
= 32 (случай, когда целое целые назначения переписываются исходным целым) из-за того, что 1 < < lt; 32 приводит к 1 (так как сдвиг обертывается вокруг) в противоположность 0.
Мои вопросы:
- Как это обычно делается? Существуют ли какие-либо особенно заметные алгоритмы (замечательно, я спрашиваю, есть ли особенно эффективные трюки, которые можно использовать для этого)?
- Работает ли мой алгоритм так же хорошо, как я думаю (он работает для всех значений, кроме = 0 и numbits = 32)?
- В связи с 1), есть ли способ сделать это только с использованием математических/побитовых операторов? Алгоритм для всех значений тривиален с использованием условий или циклов, поэтому я не заинтересован в этом.
Дизайн алгоритма, как правило, является слабым местом для меня, поэтому я не знаю, действительно ли мой алгоритм "так же хорош, как он получается", только при использовании математических/побитовых операций. Благодаря
Ответы
Ответ 1
Я не думаю, что это так, что 1 < 32 обертывания (в противном случае, почему 2 < 31 также не обертывается?) вместо этого я считаю, что внутренний модуль 32 применяется ко второму оператору, поэтому что 1 < 32 фактически эквивалентно 1 < 0. Кроме того, рассмотрите возможность изменения типов параметров с "int" на "unsigned int". Чтобы получить значение "единиц" без использования проблемы "1 < 32", вы можете сделать это:
unsigned int ones = (0xffffffff >> (32-numbits)) << at;
Я не верю, что для этого типа существуют "стандартные" методы. Я уверен, что есть другие способы использования побитовых операторов по-разному для достижения одного и того же результата, но ваш алгоритм не хуже любого.
Сказав, что, однако, важна ремонтопригодность и документация. Ваша функция выиграет от того, что алгоритм документирован с комментарием, особенно для объяснения того, как вы используете побитовый XOR - который умный, но не простой для понимания на первый взгляд.
Ответ 2
Я не думаю, что это можно сделать более эффективным, если вы не написали ассемблер.
Вы можете улучшить читаемость и решить свою проблему переполнения, изменив некоторые мелочи:
int setbits2(int destination, int source, int at, int numbits)
{
// int mask = ((1LL<<numbits)-1)<<at; // 1st aproach
int mask = ((~0u)>>(sizeof(int)*8-numbits))<<at; // 2nd aproach
return (destination&~mask)|((source<<at)&mask);
}
Более эффективная версия ассемблера (VС++):
// 3rd aproach
#define INT_SIZE 32;
int setbits3(int destination, int source, int at, int numbits)
{ __asm {
mov ecx, INT_SIZE
sub ecx, numbits
or eax, -1
shr eax, cl
mov ecx, at
shl eax, cl // mask == eax
mov ebx, eax
not eax
and eax, destination
mov edx, source
shl edx, cl
and edx, ebx
or eax, edx
}}
- 1-й aproach: медленнее на 32-битной архитектуре.
- 2nd aproach: (~ 0u) и (sizeof (int) * 8) вычисляются во время компиляции, поэтому они не берут никаких затрат.
- 3rd aproach: вы сохраняете 3 ops (обращения к памяти), записывая их на ассемблере, но вам нужно будет написать ifdefs, если вы хотите сделать его переносимым.
Ответ 3
Это довольно хорошо: я пробовал эту альтернативную версию, но ваш тест был примерно на 30% быстрее:
int[] bits = new int[] {0,1,3,7,15,31,63,127,255,511,1023
,2047,4095,8192,16383,32767,65535,131071,262143,524287
,1048575,2097151,4194303,8388607,16777215,33554431,67108863
,134217727,268435455,536870911,1073741823,2147483647,-1};
public int setbits2(int destination, int source, int at, int numbits)
{
int ones = bits[numbits + at] & ~bits[at];
return (destination & ~ones) | ((source << at) & ones);
}
Ответ 4
Обобщенная форма GRB-fnieto...
template <typename T>
T setbits4(T destination, T source, int at, int numbits)
{
T mask = (((T)-1)>>(sizeof(T)*8-numbits))<<at; // 4th aproach
return (destination&~mask)|((source<<at)&mask);
}
Ответ 5
Я думаю, что это вряд ли может быть более эффективным. Более того, побитовые операции намного быстрее, чем любые алгебраические операции.
Ответ 6
// SET OF FUNCTIONS
//########## BIT - BIT
template < typename var_t > inline var_t bit_V ( uint8_t b ) { return var_t(1) << b; } // Same as usual macros, but this one converts de variable type, so that you can use it in uint8_t to uint64_t for example.
template < typename var_t > inline var_t bit_get ( const var_t & V , uint8_t b ) { return V & bit_V<var_t>(b); } // Can be used as bool or to get the mask of the bit.
template < typename var_t > inline var_t bit_settled ( const var_t & V , uint8_t b ) { return V | bit_V<var_t>(b); }
template < typename var_t > inline var_t bit_unsettled ( const var_t & V , uint8_t b ) { return V &~ bit_V<var_t>(b); }
template < typename var_t > inline void bit_set ( var_t & V , uint8_t b ) { V |= bit_V<var_t>(b); }
template < typename var_t > inline void bit_unset ( var_t & V , uint8_t b ) { V &= ~bit_V<var_t>(b); }
template < typename var_t > inline void bit_mod ( var_t & V , uint8_t b , bool set ) { if (set) bit_set(V,b); else bit_unset(V,b); } // compiler will optimize depending on if 'set' is constant.
template < typename var_t > inline void bit_cpy ( var_t & V , const var_t & S , uint8_t b ) { var_t t = bit_get(S,b); V |= t; V &~ t; }
template < typename var_t > inline void bit_cpy ( var_t & V , const var_t & S , uint8_t bV , uint8_t bM ) { bit_mod(V,bV,bit_get(S,bM)); }
/// MULTIPLE BITS:
template < typename var_t > inline void bits_set ( var_t & V , const var_t & S ) { V |= S; }
template < typename var_t > inline void bits_unset ( var_t & V , const var_t & S ) { V &= ~S; }
/// ONLY WITH UNSIGNED INTS: 'at' parameters are refered to the less significant bit (lsb), starting at 0 index ( a byte would have 7 to 0 bits ).
template < typename var_t > void bits_cpy ( var_t & V , const var_t & S , uint8_t numBits , uint8_t atlsb = 0 ) { // I choosed not to make this one inline
var_t mask = (~var_t(0)>>(sizeof(var_t)*8 - numBits))<<atlsb;
bits_unset ( V , mask ) ;
bits_set ( V , S & mask ) ;
}
template < typename var_t > void bits_cpy ( var_t & V , const var_t & S , uint8_t numBits , uint8_t atVlsb , uint8_t atSlsb ) { // I choosed not to make this one inline
bits_cpy ( V , (atVlsb>atSlsb)?(S<<(atVlsb-atSlsb)):(S>>(atSlsb-atVlsb)) , numBits , atVlsb ) ;
}
template < typename var_t > var_t bits_cpyd ( const var_t & V , const var_t & S , uint8_t numBits , uint8_t atlsb = 0 ) {
var_t r = V;
bits_cpy (r,S,numBits,atlsb);
return r;
}
template < typename var_t > var_t bits_cpyd ( const var_t & V , const var_t & S , uint8_t numBits , uint8_t atVlsb , uint8_t atSlsb ) {
var_t r = V;
bits_cpy (r,S,numBits,atVlsb,atSlsb);
return r;
}
//########## BIT - BIT - EXAMPLE OF USE WITH THE MOST RELEVANT FUNCTIONS:
// I used them inside functions, to get/set two variables inside a class, u and c
void u_set ( edrfu_t u ) { bits_cpy <uint32_t> ( CFG , u , 8 , 2 ,0 );}
edrfu_t u_get () { return bits_cpyd <uint32_t> ( 0 , CFG , 8 , 0 ,2 );}
void c_set ( edrfc_t c ) { bits_cpy <uint32_t> ( CFG , c , 2 );}
edrfc_t c_get () { return bits_cpyd <uint32_t> ( 0 , CFG , 2 );}
Ответ 7
uint32_t
copy_bits (uint32_t dst, uint32_t src, uint8_t end_bit, uint8_t start_bit)
{
uint32_t left, right, mask, result;
if (end_bit <= start_bit)
{
printf("%s: end_bit:%d shall be greater than start_bit: %d\n", __FUNCTION__, end_bit, start_bit);
return 0;
}
left = ~0; // All Fs
right = ~0;
result = 0;
left >>= ((sizeof(uint32_t)*8) - end_bit); // Create left half of mask
right <<= start_bit; // Create right half of mask
mask = (left & right); // Now you have the mask for specific bits
result = (dst & (~mask)) | (src & (mask));
printf("%s, dst: 0x%08x, src: 0x%08x, end_bit: %d, start_bit: %d, mask: 0x%08x, result: 0x%08x\n",
__FUNCTION__, dst, src, end_bit, start_bit, mask, result);
return result;
}