Skip to Content
Course content

146: Bit Manipulation with BitOperations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time with low-level C#, you're probably comfortable with the basic bitwise operators—AND, OR, XOR, and shifts. They're great, but they only get you so far. When I need to do something more complex, like counting how many bits are set or finding the index of the first "1" in a sequence, I used to write these clumsy while loops. It was slow, error-prone, and honestly, a bit embarrassing given that modern CPUs have dedicated instructions for this.

That's where System.Numerics.BitOperations comes in. This class is essentially a wrapper around hardware intrinsics. It lets you write high-level C# that the JIT compiler turns into lightning-fast CPU instructions. I want to show you how to use it by building a simple StatusEffectManager for a game engine.

Mapping effects to a bitmask

Imagine we have a set of status effects (Poison, Burn, Frozen, etc.). Instead of a List<Effect>, which creates garbage for the GC to clean up every frame, we'll use a uint bitmask. Each bit represents one effect.

public enum StatusEffect
{
    None = 0,
    Poison = 1 << 0,
    Burn = 1 << 1,
    Frozen = 1 << 2,
    Stunned = 1 << 3,
    Hasted = 1 << 4
}

public class StatusEffectManager
{
    private uint _activeEffects = 0;

    public void AddEffect(StatusEffect effect) => _activeEffects |= (uint)effect;
    public void RemoveEffect(StatusEffect effect) => _activeEffects &= ~(uint)effect;
}


Finding the first active effect

Now, let's say the game engine needs to process the most "important" effect first. Since we've ordered our enum, the lowest bit index is the highest priority. I could write a loop to check every bit, but BitOperations.TrailingZeroCount does this in a single CPU cycle.

public int GetFirstEffectIndex()
{
    // This returns the number of zero bits before the first '1'
    // If _activeEffects is 0000 1000, it returns 3.
    return BitOperations.TrailingZeroCount(_activeEffects);
}


The "Empty Mask" trap

I actually tripped over this the first time I used this class. I wrote the code above and tested it with a few effects active. It worked perfectly. Then, I tested it with no effects active. My program crashed with an IndexOutOfRangeException.

Here is why: if the input to TrailingZeroCount is 0, it doesn't return -1 or throw an error. It returns the total number of bits in the type. For a uint, that's 32. If I'm using that result as an index into an array of effect descriptions, I'm suddenly trying to access index 32 of a 5-element array.

I had to add a guard clause to handle the "empty" state correctly:

public int GetFirstEffectIndex()
{
    if (_activeEffects == 0) return -1;
    return BitOperations.TrailingZeroCount(_activeEffects);
}


Quantifying the state with PopCount

Finally, let's say we want to know how many effects are currently active. You might be tempted to iterate through the bits, but BitOperations.PopCount (Population Count) is the standard way to count "set" bits across the industry. It's incredibly efficient.

public int GetActiveEffectCount()
{
    // Returns the number of bits set to 1.
    return BitOperations.PopCount(_activeEffects);
}


By using BitOperations, we've replaced potentially expensive loops with a few method calls that translate directly to assembly instructions like POPCNT or TZCNT. It's cleaner code and significantly better performance.




📋 Practical Task

Exercise: Building a Fast Bit-Based Resource Allocator

You are building a system to manage a pool of 32 hardware resources. A uint is used as a "busy mask," where a 1 represents a busy resource and a 0 represents an available one.

Create a class ResourceAllocator with the following requirements:

  • A private uint _busyMask.
  • A method int Allocate(): This should find the first available resource (the first 0 bit). Use BitOperations.LeadingZeroCount or TrailingZeroCount. Hint: You may need to invert the mask using the ~ operator to turn the 0s into 1s before counting. If no resources are available, return -1.
  • A method void Free(int index): This should mark the resource at the given index as available (set the bit to 0).
  • A method int GetUsageCount(): Use BitOperations.PopCount to return how many resources are currently busy.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.