Understanding Bit mask/ Bit map in depth


Reading time: 25 minutes

Bit manipulation is the act of algorithmically manipulating bits or other pieces of data shorter than a byte.

Now , What is Bit Mask?

A bit mask (or Bitmap) is a data structure, usually an integer or array of bits, that represent a bit pattern which signifies a particular state in a particular computational problem. It takes advantage of bitwise operations.

Example

Consider a situation where you have 5 computers which you assign to your guests. When a new guest arrives or some other event, you need to have information of the computers available or already assigned.

In short, we need the current state of the 5 computers.

There are many ways in which this problem can be represented like:

  • list of computer objects

We will focus on bitmasks.

In the bit mask approach, we will have a 5 bit number where the ith bit conveys information:

  • if ith bit is set to 1, ith computer is occupied
  • if ith bit is set to 0, ith computer is free

Hence, 01101 represents:

  • Computer 2, 3 and 5 are occupied
  • Computer 1 and 4 are free

Why we use bit mask?

We use bit masks because:

  • it is memory efficient in most problems

  • it is computational efficient as we can use bitwise operations which are natively supported by the computer system and will be faster than other operations like addition (+).

Common bit operations

  • Turning on bit:
    [ 0 -> 1 | 1 -> 1 ]

  • Turning off bit:
    [ 1 -> 0 | 0 -> 0 ]

  • Toggle specific bit:
    [ 1 -> 0 | 0 -> 0 ]

  • Querying Bit:
    [ Check whether particular bit is 0 or 1 ]

bit Operators :

  • (&) - Bitwise AND
  • (|) - Bitwise OR
  • (^) - Bitwise XOR (Exclusive OR)
  • (!) - Bitwise complement
  • (<<) - Left Shift
  • (>>) - Right Shift

bitwiseOperations

Example :

First Let's take one random number:

178 // base 10 (decimal)
10110010 // base 2 (binary)

data = 178

I will use this number through out the article

1) Quering Bit :

Quering Bit means check particular bit whether it is 0 or 1 at desired position. For that first i have to build a mask.

  • Let's check 3rd bit is 0 or 1
// Build a Mask
mask = 00000100  

How? We can have given mask by left shift of 1 by 2 times that is,

mask = 1 << 2
mask = 00000100

Now , AND(&) of data with mask

data    10110010
mask  & 00000100
        ________
ans =   00000000

if(ans is ZERO)
desired bit is zero (0)

if(ans is NON ZERO)
desired bit is one (1)

ans = 0 (base 10)
here, ans is ZERO so, 3rd bit is 0 :)

Why? Here we have used AND(&) operator...
x & 0 = 0
x & 1 = x (x = 0/1)

  • Let's check 5th bit is 0 or 1

Same procedure ,

// Build a mask
mask = 1 << 4
mask = 00010000

// AND(&) of data with mask

data    10110010
mask  & 00010000
        ________
ans =   00010000     

ans = 16 (base 10)
here, ans is NON ZERO so, 5th bit is 1 :)

Hack is we are anding(&) by 1 only with our desired bit so if it is 1 then answer will obvious non zero and if it is zero then all bits in answer will be zero.
So , answer (data & mask) is NON_ZERO iff desired bit is set or 1. If answer is zero then desired bit is 0.

2) Turning Bit On :

If we want to set perticular bit to 1 then Bit Mask will help us.

  • Let's set 7th bit to 1
// Build a Mask
mask = 01000000  (7th bit 1 others 0)

// OR(|) of data with mask
data    10110010
mask  | 01000000
        ________
ans =   11110010

Why? Here we have used OR(|) operation

Hack is OR(|) will give ans 0 iff both bits are 0 otherwise it will gives 1

x | 0 = x
x | 1 = 1 (x = 0/1)

Hack is if we do OR(|) of 1 with anything (0/1) it will set ans to 1. So, here we are doing OR(|) operation with our desired bit and it will set that bit to 1. And if We do OR(|) by 0 with any other bit it it will not change that bit.

3) Turning Bit Off :

If we want to set perticular bit to 0 then Bit Mask will help us.

  • Let's set 2nd bit to 0
// Build a Mask
mask = 11111101  (2nd bit 0 others 1)

How? 
mask = ~(1<<2)  [Bitwise complement(~) operator : it will flip all bits ~(11110000) = (00001111)]
mask = !(00000010)
mask = 11111101

// AND(&) of data with mask
data    10110010
mask  | 11111101
        ________
ans =   10110000

Got it?? Because we have used AND(&) :)

Hack is if we do AND(&) of 0 with anything (0/1) it will set ans to 0. So, here we are doing AND(&) operation with our desired bit and it will set that bit to 0. And if We do AND(&) by 0 with any other bit it it will not change that bit.

4) Toggling Bit :

If we want to toggle bits that is 0->1 or 1->0 then Bit Mask will help us.

  • Let's toggle 4 bits [1,2,6,7] of data (10110010)

// Build a Mask
mask = 01100011 (1st , 2nd , 6th , 7th bits are 1 others are 0)

How?

mask = (1<<6) | (1<<5) | (1<<1) | (1<<0)
mask = 01100011

// XOR(^) of data with mask
data    10110010
mask  ^ 01100011
        ________
ans =   11010001

Why? Because we have used XOR(^) operator

Hack is if we do XOR(^) of 0 with anything (0/1) it will not change that bit. So, here we are doing XOR(^) operation with our desired bit and it will put it as it is. And if We do XOR(^) by 1 with any other bit it it will flip that bit.

x ^ 0 = x [0^0 = 0 , 1^0 = 1]
x ^ 1 = ~x [0^1 = 1 , 1^1 = 0] x = {0/1}

Code example

I suggest you go through code it is very simple. I have made function for all above operations. You have to pass data and position of bit on which you want to perform operation.

    #include <bits/stdc++.h> 
    using namespace std; 

    void queryBit(int data , int position){
        int mask,ans;
    
        cout << "Check for " << position << " Bit" << endl;
    
        // storing data
        data = 178;   
        cout << "data  :  " << bitset<8>(data) << endl;

        // Build Mask
        mask = 1 << (position-1);
        cout << "mask  :  " << bitset<8>(mask) << endl;

        // Bitwise AND(&) between data and mask
        ans  = data & mask;  
        cout << "ans   :  " << bitset<8>(ans) << endl;

        if(ans == 0)  // If ZERO then desired bit is 0
            cout << "Bit at position " << position << " is = " << 0 << endl;
        else  // If NON_ZERO then desired bit is 1
            cout << "Bit at position " << position << " is = " << 1 << endl;

        cout << endl;
    }

    void setTo1(int data , int position){
        int mask,ans;

        cout << "Set to 1 at position " << position << endl;

        // storing data
        data = 178;   
        cout << "data  :  " << bitset<8>(data) << endl;

        // Build Mask
        mask = 1 << (position-1);
        cout << "mask  :  " << bitset<8>(mask) << endl;

        // Bitwise OR(|) between data and mask
        ans  = data | mask;  
        cout << "ans   :  " << bitset<8>(ans) << endl;

        cout << endl;
    }

    void setTo0(int data , int position){
        int mask,ans;

        cout << "Set to 0 at position " << position << endl;

        // storing data
        data = 178;   
        cout << "data  :  " << bitset<8>(data) << endl;

        // Build Mask
        mask = ~(1 << (position-1));
        cout << "mask  :  " << bitset<8>(mask) << endl;

        // Bitwise AND(&) between data and mask
        ans  = data & mask;  
        cout << "ans   :  " << bitset<8>(ans) << endl;

        cout << endl;
    }

    void toggleBits(int data , int position){
        int mask,ans;

        cout << "Toggle bit at position " << position << endl;

        // storing data
        data = 178;   
        cout << "data  :  " << bitset<8>(data) << endl;

        // Build Mask
        mask = 1 << (position-1);
        cout << "mask  :  " << bitset<8>(mask) << endl;

        // Bitwise XOR(^) between data and mask
        ans  = data ^ mask;  
        cout << "ans   :  " << bitset<8>(ans) << endl;

        cout << endl;
    }

    int main(){ 

        int data = 178;          // our data - 10110010

        queryBit(data,2);
        queryBit(data,3);

        setTo0(data,2);
        setTo0(data,3);

        setTo1(data,2);
        setTo1(data,3);

        toggleBits(data,2);
        toggleBits(data,3);

        return 0; 
    }

Output:

Check for 2 Bit
data  :  10110010
mask  :  00000010
ans   :  00000010
Bit at position 2 is = 1

Check for 3 Bit
data  :  10110010
mask  :  00000100
ans   :  00000000
Bit at position 3 is = 0

Set to 0 at position 2
data  :  10110010
mask  :  11111101
ans   :  10110000

Set to 0 at position 3
data  :  10110010
mask  :  11111011
ans   :  10110010

Set to 1 at position 2
data  :  10110010
mask  :  00000010
ans   :  10110010

Set to 1 at position 3
data  :  10110010
mask  :  00000100
ans   :  10110110

Toggle bit at position 2
data  :  10110010
mask  :  00000010
ans   :  10110000

Toggle bit at position 3
data  :  10110010
mask  :  00000100
ans   :  10110110

Practical use

  • In programming languages such as C, bit fields are a useful way to pass a set of named boolean arguments to a function.[2]

  • Masks are used with IP addresses in IP ACLs (Access Control Lists) to specify what should be permitted and denied.[2]

  • In computer graphics, when a given image is intended to be placed over a background, the transparent areas can be specified through a binary mask.[2]