web-dev-qa-db-ja.com

Cのビットマスク

mセットビットの前にk unsetビットが続き、その後にn unsetビットが続くCでビットマスクを作成する最良の方法は何ですか?

00..0 11..1 00..0
  k     m     n

たとえば、k = 1、m = 4、n = 3の場合、ビットマスクは次のようになります。

01111000
22
grigy

〜(〜0 << m)<< n

41
Darius Bacon

それで、k個のリセットビットが前に付いてn個のリセットビットが続くm個のセットビットを求めていますか? kは整数型の選択によって大きく制約されるため、無視できます。

mask = ((1 << m) - 1) << n;
29

私は両方のソリューションが好きです。これが私の頭に浮かぶ別の方法です(おそらく良くはありません)。

((~((unsigned int)0) << k) >> (k + n)) << n

編集:以前のバージョンにはバグがありました(unsigned intキャストがありませんでした)。問題は~0 >> nは、0ではなく1を先頭に追加します。

そして、はい、このアプローチには1つの大きな欠点があります。デフォルトの整数型のビット数を知っていることを前提としています。つまり、実際にkを知っていることを前提としていますが、他のソリューションはkに依存していません。これにより、私のバージョンの移植性が低下するか、少なくとも移植が困難になります。 (また、3つのシフト、加算、および2つの追加操作であるビット単位の否定演算子を使用します。)

したがって、他の例の1つを使用する方がよいでしょう。

これは、さまざまなソリューションの出力を比較および検証するためにJonathanLefflerによって実行された小さなテストアプリです。

#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;
}
5
quinmars

(のみ)BMI2をサポートするx86システム(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は3つの命令にコンパイルされます

_movl    $-1, %edx
bzhi    %edi, %edx, %edi
shlx    %esi, %edi, %eax
_

@ Jonathan Leffler および @ Darius Bacon によるコードよりも1命令少ないです。 Intel Haswellプロセッサ以降では、bzhishlxの両方のレイテンシが1サイクルで、スループットが1サイクルあたり2です。 AMD Ryzenでは、これら2つの命令のスループットは1サイクルあたり4です。

1
wim

上位の回答は単純で効果的ですが、_n=0_および_m=31_の場合のMSBを設定しません。

~(~0 << 31) << 0 = _0111 1111 1111 1111 1111 1111 1111 1111‬_

_((1 << 31)-1) << 0_ = _0111 1111 1111 1111 1111 1111 1111 1111‬_

32ビットの符号なしWord(醜くて分岐がある)に対する私の提案は次のようになります。

_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_。

0
Nubcake