web-dev-qa-db-fra.com

Comment définir et travailler avec un tableau de bits en C?

Je veux créer un très grand tableau sur lequel j'écris des "0" et des "1". J'essaie de simuler un processus physique appelé adsorption séquentielle aléatoire, où des unités de longueur 2, des dimères, sont déposées sur un réseau à n dimensions à un endroit aléatoire, sans se chevaucher. Le processus s'arrête lorsqu'il n'y a plus de place sur le réseau pour déposer plus de dimères (le réseau est coincé).

Au début, je commence par un réseau de zéros, et les dimères sont représentés par une paire de "1". Lorsque chaque dimère est déposé, le site à gauche du dimère est bloqué, car les dimères ne peuvent pas se chevaucher. Je simule donc ce processus en déposant un triple de 1 sur le réseau. Je dois répéter toute la simulation un grand nombre de fois, puis calculer le pourcentage de couverture moyen.

J'ai déjà fait cela en utilisant un tableau de caractères pour les réseaux 1D et 2D. En ce moment, j'essaie de rendre le code aussi efficace que possible, avant de travailler sur le problème 3D et les généralisations plus compliquées.

Voici à quoi ressemble le code dans 1D, simplifié:

int main()
{
    /* Define lattice */
    array = (char*)malloc(N * sizeof(char));

    total_c = 0;

    /* Carry out RSA multiple times */
    for (i = 0; i < 1000; i++)
        Rand_seq_ads();

    /* Calculate average coverage efficiency at jamming */
    printf("coverage efficiency = %lf", total_c/1000);

    return 0;
}

void Rand_seq_ads()
{
    /* Initialise array, initial conditions */
    memset(a, 0, N * sizeof(char));
    available_sites = N;
    count = 0;

    /* While the lattice still has enough room... */
    while(available_sites != 0)
    {
        /* Generate random site location */
        x = Rand();

        /* Deposit dimer (if site is available) */
        if(array[x] == 0)
        {
            array[x] = 1;
            array[x+1] = 1;
            count += 1;
            available_sites += -2;
        }

        /* Mark site left of dimer as unavailable (if its empty) */
        if(array[x-1] == 0)
        {
            array[x-1] = 1;
            available_sites += -1;
        }
    }

    /* Calculate coverage %, and add to total */
    c = count/N
    total_c += c;
}

Pour le projet réel que je fais, cela implique non seulement des gradateurs, mais des trimers, des quadrimères et toutes sortes de formes et de tailles (pour 2D et 3D).

J'espérais pouvoir travailler avec des bits individuels au lieu d'octets, mais j'ai lu autour et pour autant que je sache, vous ne pouvez changer qu'un octet à la fois, donc je dois faire une indexation compliquée ou il existe un moyen plus simple de le faire?

Merci pour vos réponses

39
Eddy

Si je ne suis pas trop tard, cette page donne une explication impressionnante avec des exemples.

Un tableau de int peut être utilisé pour gérer un tableau de bits. En supposant que la taille de int soit 4 bytes, lorsque nous parlons d'un int, nous avons affaire à 32 bits. Disons que nous avons int A[10], signifie que nous travaillons sur 10*4*8 = 320 bits et la figure suivante le montre: (chaque élément du tableau a 4 gros blocs, chacun représentant un byte et chacun des petits blocs représente un bit)

enter image description here

Ainsi, pour définir le kème bit dans le tableau A:

void  SetBit( int A[],  int k )
   {
      int i = k/32;        //gives the corresponding index in the array A
      int pos = k%32;      //gives the corresponding bit position in A[i]

      unsigned int flag = 1;   // flag = 0000.....00001

      flag = flag << pos;      // flag = 0000...010...000   (shifted k positions)

      A[i] = A[i] | flag;      // Set the bit at the k-th position in A[i]
   }

ou dans la version raccourcie

void  SetBit( int A[],  int k )
   {
      A[k/32] |= 1 << (k%32);  // Set the bit at the k-th position in A[i]
   }

de la même façon pour effacer kth bit:

void  ClearBit( int A[],  int k )                
   {
      A[k/32] &= ~(1 << (k%32));
   }

et pour tester si le kème bit:

int TestBit( int A[],  int k )
   {
      return ( (A[k/32] & (1 << (k%32) )) != 0 ) ;     
   }

Comme indiqué ci-dessus, ces manipulations peuvent également être écrites sous forme de macros:

#define SetBit(A,k)     ( A[(k/32)] |= (1 << (k%32)) )
#define ClearBit(A,k)   ( A[(k/32)] &= ~(1 << (k%32)) )            
#define TestBit(A,k)    ( A[(k/32)] & (1 << (k%32)) )
43
aniliitb10
typedef unsigned long bfield_t[ size_needed/sizeof(long) ];
// long because that's probably what your cpu is best at
// The size_needed should be evenly divisable by sizeof(long) or
// you could (sizeof(long)-1+size_needed)/sizeof(long) to force it to round up

Maintenant, chaque long dans un bfield_t peut contenir sizeof (long) * 8 bits.

Vous pouvez calculer l'indice d'un grand nécessaire en:

bindex = index / (8 * sizeof(long) );

et votre numéro de bit par

b = index % (8 * sizeof(long) );

Vous pouvez ensuite rechercher le temps dont vous avez besoin, puis masquer le peu dont vous avez besoin.

result = my_field[bindex] & (1<<b);

ou

result = 1 & (my_field[bindex]>>b); // if you prefer them to be in bit0

Le premier peut être plus rapide sur certains processeurs ou peut vous éviter de reculer de votre besoin d'effectuer des opérations entre le même bit dans plusieurs tableaux de bits. Il reflète également le réglage et l'effacement d'un bit sur le terrain de plus près que la deuxième implémentation. ensemble:

my_field[bindex] |= 1<<b;

clair:

my_field[bindex] &= ~(1<<b);

Vous devez vous rappeler que vous pouvez utiliser des opérations au niveau du bit sur les longs qui contiennent les champs et c'est la même chose que les opérations sur les bits individuels.

Vous voudrez probablement également examiner les fonctions ffs, fls, ffc et flc si elles sont disponibles. ffs devrait toujours être disponible dans strings.h. Il est là juste à cet effet - une chaîne de bits. Quoi qu'il en soit, il s'agit de trouver le premier set et essentiellement:

int ffs(int x) {
    int c = 0;
    while (!(x&1) ) {
        c++;
        x>>=1;
    }
    return c; // except that it handles x = 0 differently
}

C'est une opération courante pour les processeurs d'avoir une instruction pour et votre compilateur va probablement générer cette instruction plutôt que d'appeler une fonction comme celle que j'ai écrite. Soit dit en passant, x86 a une instruction pour cela. Oh, et ffsl et ffsll sont la même fonction sauf prendre long et long long, respectivement.

9
nategoose

Vous pouvez utiliser & (au niveau du bit et) et << (décalage vers la gauche).

Par exemple, (1 << 3) donne "00001000" en binaire. Ainsi, votre code pourrait ressembler à:

char eightBits = 0;

//Set the 5th and 6th bits from the right to 1
eightBits &= (1 << 4);
eightBits &= (1 << 5);
//eightBits now looks like "00110000". 

Il suffit ensuite de le mettre à l'échelle avec un tableau de caractères et de trouver l'octet approprié à modifier en premier.

Pour plus d'efficacité, vous pouvez définir à l'avance une liste de champs binaires et les placer dans un tableau:

#define BIT8 0x01
#define BIT7 0x02
#define BIT6 0x04
#define BIT5 0x08
#define BIT4 0x10
#define BIT3 0x20
#define BIT2 0x40
#define BIT1 0x80

char bits[8] = {BIT1, BIT2, BIT3, BIT4, BIT5, BIT6, BIT7, BIT8};

Ensuite, vous évitez les frais généraux du décalage de bits et vous pouvez indexer vos bits, transformant le code précédent en:

eightBits &= (bits[3] & bits[4]);

Alternativement, si vous pouvez utiliser C++, vous pouvez simplement utiliser un std::vector<bool> qui est défini en interne comme un vecteur de bits, avec indexation directe.

6
David

bitarray.h :

#include <inttypes.h> // defines uint32_t

//typedef unsigned int bitarray_t; // if you know that int is 32 bits
typedef uint32_t bitarray_t;

#define RESERVE_BITS(n) (((n)+0x1f)>>5)
#define DW_INDEX(x) ((x)>>5)
#define BIT_INDEX(x) ((x)&0x1f)
#define getbit(array,index) (((array)[DW_INDEX(index)]>>BIT_INDEX(index))&1)
#define putbit(array, index, bit) \
    ((bit)&1 ?  ((array)[DW_INDEX(index)] |= 1<<BIT_INDEX(index)) \
             :  ((array)[DW_INDEX(index)] &= ~(1<<BIT_INDEX(index))) \
             , 0 \
    )

Utilisation:

bitarray_t arr[RESERVE_BITS(130)] = {0, 0x12345678,0xabcdef0,0xffff0000,0};
int i = getbit(arr,5);
putbit(arr,6,1);
int x=2;            // the least significant bit is 0
putbit(arr,6,x);    // sets bit 6 to 0 because 2&1 is 0
putbit(arr,6,!!x);  // sets bit 6 to 1 because !!2 is 1

MODIFIER les documents:

"dword" = "double Word" = valeur 32 bits (non signé, mais ce n'est pas vraiment important)

RESERVE_BITS: number_of_bits --> number_of_dwords
    RESERVE_BITS(n) is the number of 32-bit integers enough to store n bits
DW_INDEX: bit_index_in_array --> dword_index_in_array
    DW_INDEX(i) is the index of dword where the i-th bit is stored.
    Both bit and dword indexes start from 0.
BIT_INDEX: bit_index_in_array --> bit_index_in_dword
    If i is the number of some bit in the array, BIT_INDEX(i) is the number
    of that bit in the dword where the bit is stored.
    And the dword is known via DW_INDEX().
getbit: bit_array, bit_index_in_array --> bit_value
putbit: bit_array, bit_index_in_array, bit_value --> 0

getbit(array,i) récupère le dword contenant le bit i et décale le dword vers la droite , de sorte que le bit i devienne le bit le moins significatif. Ensuite, un au niveau du bit et avec 1 efface tous les autres bits.

putbit(array, i, v) vérifie tout d'abord le bit de v le moins significatif; s'il vaut 0, nous devons effacer le bit, et s'il vaut 1, nous devons le régler.
Pour régler le bit, nous faisons un au niveau du bit ou du mot qui contient le bit et la valeur de 1 décalé vers la gauche par bit_index_in_dword: ce bit est activé et les autres bits ne changent pas.
Pour effacer le bit, nous faisons un au niveau du bit et du dword qui contient le bit et le complément au niveau du bit de 1 décalé vers la gauche par bit_index_in_dword: cette valeur a tous les bits mis à un sauf le seul bit zéro dans la position que nous voulons effacer.
La macro se termine par , 0 Car sinon elle retournerait la valeur de dword où le bit i est stocké, et cette valeur n'a pas de sens. On pourrait également utiliser ((void)0).

5

C'est un compromis:

(1) utilisez 1 octet pour chaque valeur de 2 bits - simple, rapide, mais utilise une mémoire 4x

(2) regrouper les bits en octets - plus complexe, une certaine surcharge de performance, utilise un minimum de mémoire

Si vous avez suffisamment de mémoire disponible, optez pour (1), sinon pensez à (2).

2
Paul R