Каков наилучший способ построения битовой маски в C с m
битами набора, которым предшествует k
unset bits, а затем n
unset bits:
00..0 11..1 00..0
k m n
Например, k = 1, m = 4, n = 3 приведет к битовой маске:
01111000
Каков наилучший способ построения битовой маски в C с m
битами набора, которым предшествует k
unset bits, а затем n
unset bits:
00..0 11..1 00..0
k m n
Например, k = 1, m = 4, n = 3 приведет к битовой маске:
01111000
~ (~ 0 < m) < п
Итак, вы запрашиваете m битов с префиксом k reset bits, а затем n reset bits? Мы можем игнорировать k, поскольку он будет в значительной степени ограничен выбором целочисленного типа.
mask = ((1 << m) - 1) << n;
Мне нравятся оба решения. Вот еще один способ, который приходит мне на ум (возможно, не лучше).
((~((unsigned int)0) << k) >> (k + n)) << n
EDIT:
В моей предыдущей версии произошла ошибка (это было без трансляции без знака). Проблема заключалась в том, что ~0 >> n
добавляет 1s спереди, а не 0s.
И да, этот подход имеет один большой недостаток; он предполагает, что вы знаете количество бит целочисленного типа по умолчанию или, другими словами, оно предполагает, что вы действительно знаете k, тогда как другие решения не зависят от k. Это делает мою версию менее переносной или, по крайней мере, труднее переносить. (Он также использует 3 смены и добавление и оператор побитового отрицания, что является двумя дополнительными операциями.)
Таким образом, вам лучше использовать один из других примеров.
Вот небольшое тестовое приложение, сделанное Джонатаном Леффлером, для сравнения и проверки вывода различных решений:
#include <stdio.h>
#include <limits.h>
enum { ULONG_BITS = (sizeof(unsigned long) * CHAR_BIT) };
static unsigned long set_mask_1(int k, int m, int n)
{
return ~(~0 << m) << n;
}
static unsigned long set_mask_2(int k, int m, int n)
{
return ((1 << m) - 1) << n;
}
static unsigned long set_mask_3(int k, int m, int n)
{
return ((~((unsigned long)0) << k) >> (k + n)) << n;
}
static int test_cases[][2] =
{
{ 1, 0 },
{ 1, 1 },
{ 1, 2 },
{ 1, 3 },
{ 2, 1 },
{ 2, 2 },
{ 2, 3 },
{ 3, 4 },
{ 3, 5 },
};
int main(void)
{
size_t i;
for (i = 0; i < 9; i++)
{
int m = test_cases[i][0];
int n = test_cases[i][1];
int k = ULONG_BITS - (m + n);
printf("%d/%d/%d = 0x%08lX = 0x%08lX = 0x%08lX\n", k, m, n,
set_mask_1(k, m, n),
set_mask_2(k, m, n),
set_mask_3(k, m, n));
}
return 0;
}
В то время как верхние ответы просты и эффективны, они не устанавливают MSB для случая, когда n=0
и m=31
:
~(~0 << 31) << 0
= 0111 1111 1111 1111 1111 1111 1111 1111
((1 << 31)-1) << 0
= 0111 1111 1111 1111 1111 1111 1111 1111
Мое предложение для 32-битного слова без знака (которое является уродливым и имеет ветку) выглядит следующим образом:
unsigned int create_mask(unsigned int n,unsigned int m) {
// 0 <= start_bit, end_bit <= 31
return (m - n == 31 ? 0xFFFFFFFF : ((1 << (m-n)+1)-1) << n);
}
Это фактически получает биты в диапазоне [m,n]
(закрытый интервал), поэтому create_mask(0,0)
вернет маску для первого бита (бит 0) и create_mask(4,6)
возвращает маску для бит с 4 по 6, т.е. ... 00111 0000
.
(только) Для тех, кто заинтересован в немного более эффективном решении на системах x86 с поддержкой BMI2 (Intel Haswell или новее, AMD Excavator или новее):
mask = _bzhi_u32(-1,m)<<n;
Команда bzhi
нули старших бит, начиная с указанной позиции бита.
Собственные компиляции _bzhi_u32
к этой инструкции. Тестовый код:
#include <stdio.h>
#include <x86intrin.h>
/* gcc -O3 -Wall -m64 -march=haswell bitmsk_mn.c */
unsigned int bitmsk(unsigned int m, unsigned int n)
{
return _bzhi_u32(-1,m)<<n;
}
int main() {
int k = bitmsk(7,13);
printf("k= %08X\n",k);
return 0;
}
Вывод:
$./a.out
k= 000FE000
Фрагмент кода _bzhi_u32(-1,m)<<n
компилируется в три команды
movl $-1, %edx
bzhi %edi, %edx, %edi
shlx %esi, %edi, %eax
Что на одну инструкцию меньше, чем коды @Jonathan Leffler
и @Darius Bacon.
На процессорах Intel Haswell или новее оба bzhi
и shlx
имеют задержку в 1 цикл и
пропускная способность 2 за цикл. На AMD Ryzen эти две инструкции имеют пропускную способность 4 за цикл.