|
| 1 | +# Understanding Bit Operation |
| 2 | + |
| 3 | +## Bit Shifting |
| 4 | + |
| 5 | +### Left & Right Shifting |
| 6 | + |
| 7 | +Left shifting moves the whole bit sequence with specific count to left, `0` will fill up the tail and the leading bits are just discarded. |
| 8 | + |
| 9 | +```cs |
| 10 | + |
| 11 | +uint before = 0b_1001_0000_0000_0000_0000_0000_0000_1001; |
| 12 | +uint after = before << 4; |
| 13 | +Console.WriteLine($"{nameof(before),6}: {before,32:B32}"); |
| 14 | +Console.WriteLine($"{nameof(after),6}: {after:B32}"); |
| 15 | + |
| 16 | +// before: 10010000000000000000000000001001 |
| 17 | +// ^^^^ ^^^^ |
| 18 | +// discarded move left |
| 19 | +
|
| 20 | +// after: 00000000000000000000000010010000 |
| 21 | +// ^^^^ |
| 22 | +``` |
| 23 | + |
| 24 | +Right shifting does it reversely. |
| 25 | +The only difference is, when shifting on *signed* integers, bits would be filled with the sign bit value(`0` for positive, `1` for negative) |
| 26 | + |
| 27 | +```cs |
| 28 | +sbyte before = sbyte.MinValue; |
| 29 | +sbyte after = (sbyte)(before >> 4); |
| 30 | +Console.WriteLine($"{nameof(before),6}: {before:B8}"); |
| 31 | +Console.WriteLine($"{nameof(after),6}: {after:B8}"); |
| 32 | + |
| 33 | +``` |
| 34 | + |
| 35 | +> [!NOTE] |
| 36 | +> Left & Right Shifting supports only `int`, `uint`, `long`, `ulong`. If you perform shifting on other integer types, the leftoperand is converted as `int` |
| 37 | +
|
| 38 | +> [!NOTE] |
| 39 | +> If shift count exceeds the bit width or is even negative, the count is determined by |
| 40 | +> - if leftoperand is `int` or `uint`: `count = count & 0b_1_1111` |
| 41 | +> - if leftoperand is `long` or `ulong`: `count = count & 0b_11_1111` |
| 42 | +
|
| 43 | +> [!NOTE] |
| 44 | +> Left shifting might discard sign of the number when it's signed |
| 45 | +>```cs |
| 46 | +>int foo = -0b_0001_0001_0001_0001_0001_0001_0001_0001_01; |
| 47 | +>Console.WriteLine(foo.ToString("B32")); |
| 48 | +>Console.WriteLine((foo << 1).ToString("B32")); |
| 49 | +>Console.WriteLine(int.IsPositive(foo << 1)); |
| 50 | +
|
| 51 | +>// 10111011101110111011101110111011 |
| 52 | +>// 01110111011101110111011101110110 |
| 53 | +>// True |
| 54 | +>``` |
| 55 | +
|
| 56 | +### Unsigned Right Shifting |
| 57 | +
|
| 58 | +> [!NOTE] |
| 59 | +> `>>>` was supported since C# 11 |
| 60 | +
|
| 61 | +If you want to make sure that the empty bits are filled with `0` instead of value of the sign when working with the signed integers, use `>>>`! |
| 62 | +
|
| 63 | +```cs |
| 64 | +int before = int.MinValue; |
| 65 | +int after = before >> 2; |
| 66 | +int afterUnsigned = before >>> 2; // [!code highlight] |
| 67 | +Console.WriteLine($"{nameof(before),-20}: {before:B32}"); |
| 68 | +Console.WriteLine($"{nameof(after),-20}: {after:B32}"); |
| 69 | +Console.WriteLine($"{nameof(afterUnsigned),-20}: {afterUnsigned:B32}"); |
| 70 | +
|
| 71 | +// before : 10000000000000000000000000000000 |
| 72 | +// after : 11100000000000000000000000000000 |
| 73 | +// afterUnsigned : 00100000000000000000000000000000 // [!code highlight] |
| 74 | +``` |
| 75 | +
|
| 76 | +> [!NOTE] |
| 77 | +> Before `>>>` was added, you had to perform casting to achieve the same |
| 78 | +>```cs |
| 79 | +>int afterUnsigned = unchecked((int)((uint)before >> 2)); |
| 80 | +>``` |
| 81 | +
|
| 82 | +## Bitwise NOT |
| 83 | +
|
| 84 | +Reverse value of each bit |
| 85 | +
|
| 86 | +```cs |
| 87 | +uint before = 0b_0100010; |
| 88 | +uint after = ~before; |
| 89 | +Console.WriteLine($"{nameof(before),6}: {before,32:B32}"); |
| 90 | +Console.WriteLine($"{nameof(after),6}: {after:B}"); |
| 91 | +// before: 00000000000000000000000000100010 |
| 92 | +// after: 11111111111111111111111111011101 |
| 93 | +``` |
| 94 | +
|
| 95 | +## Bitwise OR & AND & XOR |
| 96 | + |
| 97 | +- bitwise OR `|`: returns `1` for each bit as long as one of the two is `1`, else `0` |
| 98 | +- bitwise AND `&`: returns `1` for each bit as long as all of the two is `1`, else `0` |
| 99 | +- bitwise XOR `^`: returns `1` if the two bits are different, `0` when identical |
| 100 | + |
| 101 | + |
| 102 | +## Bitwise on Enum |
| 103 | + |
| 104 | +Enum can be flags when the type is marked with `FlagsAttribute` and ordinary enum members are powers of 2(members represent the `All` or `None` are exceptional) |
| 105 | + |
| 106 | +```cs |
| 107 | +[Flags] |
| 108 | +enum Foo |
| 109 | +{ |
| 110 | + None = 0b0000, |
| 111 | + Bar = 0b0001, |
| 112 | + Baz = 0b0010, |
| 113 | + Qux = 0b0100, |
| 114 | + Goo = 0b1000, |
| 115 | + All = 0b1111 |
| 116 | +} |
| 117 | +// powers can be easily done by left shifting |
| 118 | +[Flags] |
| 119 | +enum Foo |
| 120 | +{ |
| 121 | + None = 0, |
| 122 | + Bar = 1, |
| 123 | + Baz = 1 << 1, |
| 124 | + Qux = 1 << 2, |
| 125 | + Goo = 1 << 3, |
| 126 | + All = ~(~0 << 4) |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +## Bit Mask |
| 131 | + |
| 132 | +Bit mask is a common pattern that works like a filter to include or exclude or test bits. |
| 133 | +The mask can refer to a integer represented as binary, a sequence of integers or a matrix of integers. The classical usage is a singular number. |
| 134 | + |
| 135 | +### Is Bit Set |
| 136 | + |
| 137 | +A common usage of mask is check whether specific bit **is set** |
| 138 | + |
| 139 | +- when a bit is `1` meaning that it **is set** |
| 140 | +- when a bit is `0` meaning that it **is cleared** |
| 141 | + |
| 142 | +A mask is a predefined number with special layout designated to solve specific problem. |
| 143 | +The following example shows how a mask is generated for the purpose. |
| 144 | + |
| 145 | +`1` is shifted left to the position we would like to compare, and a bitwise AND is performed. |
| 146 | +Only the position matters since other bits are all `0` in the mask. |
| 147 | +So only when the bit on the position is set can the operation returns non-zero value. |
| 148 | + |
| 149 | +```cs |
| 150 | +uint number = 0b_0101; |
| 151 | +int position = 2; |
| 152 | +int mask = 1 << position; // generate a special number 0100 |
| 153 | +bool positionIsSet = (number & mask) != 0; |
| 154 | +``` |
| 155 | + |
| 156 | +This is the particular same case as how we tell whether a union of enum contains a enum flag. |
| 157 | +Since each enum member has **only one bit set**, the union in the following example has two bits set, only when the set bit from the flag being checked overlaps with any bit of the union can it evaluate to non-zero. |
| 158 | +And in fact the operation `(union & flag)` should be equal to the flag itself. |
| 159 | + |
| 160 | +> [!WARNING] |
| 161 | +> You have to make sure the enum member ot integer to be checked is a valid member or it may evaluate to unexpected value. |
| 162 | +
|
| 163 | +```cs |
| 164 | +Foo foo = Foo.Bar | Foo.Qux; |
| 165 | +_ = (foo & Foo.Qux) != 0; // True |
| 166 | +_ = (foo & Foo.Qux) == Foo.Qux; // True |
| 167 | +_ = (foo & Foo.Goo) != 0; // False |
| 168 | +_ = (foo & Foo.Goo) == Foo.Goo; // False |
| 169 | +
|
| 170 | +[Flags] |
| 171 | +enum Foo |
| 172 | +{ |
| 173 | + None = 0, |
| 174 | + Bar = 1, |
| 175 | + Baz = 1 << 1, |
| 176 | + Qux = 1 << 2, |
| 177 | + Goo = 1 << 3, |
| 178 | + All = ~(~0 << 4) |
| 179 | +} |
| 180 | +``` |
0 commit comments