I am working on a little Hardware interface project based on the Velleman k8055 board.
The example code comes in VB.Net and I'm rewriting this into C#, mostly to have a chance to step through the code and make sense of it all.
One thing has me baffled though:
At one stage they read all digital inputs and then set a checkbox based on the answer to the read digital inputs (which come back in an Integer) and then they AND this with a number:
i = ReadAllDigital
cbi(1).Checked = (i And 1)
cbi(2).Checked = (i And 2) \ 2
cbi(3).Checked = (i And 4) \ 4
cbi(4).Checked = (i And 8) \ 8
cbi(5).Checked = (i And 16) \ 16
I have not done Digital systems in a while and I understand what they are trying to do but what effect would it have to AND two numbers? Doesn't everything above 0 equate to true?
How would you translate this to C#?
This is doing a bitwise AND, not a logical AND.
Each of those basically determines whether a single bit in i is set, for instance:
5 AND 4 = 4
5 AND 2 = 0
5 AND 1 = 1
(Because 5 = binary 101, and 4, 2 and 1 are the decimal values of binary 100, 010 and 001 respectively.)
I think you 'll have to translate it to this:
i & 1 == 1
i & 2 == 2
i & 4 == 4
etc...
This is using the bitwise AND operator.
When you use the bitwise AND operator, this operator will compare the binary representation of the two given values, and return a binary value where only those bits are set, that are also set in the two operands.
For instance, when you do this:
2 & 2
It will do this:
0010 & 0010
And this will result in:
0010
0010
&----
0010
Then if you compare this result with 2 (0010), it will ofcourse return true.
Just to add:
It's called bitmasking
http://en.wikipedia.org/wiki/Mask_(computing)
A boolean only require 1 bit. In the implementation most programming language, a boolean takes more than a single bit. In PC this won't be a big waste, but embedded system usually have very limited memory space, so the waste is really significant. To save space, the booleans are packed together, this way a boolean variable only takes up 1 bit.
You can think of it as doing something like an array indexing operation, with a byte (= 8 bits) becoming like an array of 8 boolean variables, so maybe that's your answer: use an array of booleans.
Think of this in binary e.g.
10101010
AND
00000010
yields 00000010
i.e. not zero. Now if the first value was
10101000
you'd get
00000000
i.e. zero.
Note the further division to reduce everything to 1 or 0.
(i and 16) / 16 extracts the value (1 or 0) of the 5th bit.
1xxxx and 16 = 16 / 16 = 1
0xxxx and 16 = 0 / 16 = 0
And operator performs "...bitwise conjunction on two numeric expressions", which maps to '|' in C#. The '` is an integer division, and equivalent in C# is /, provided that both operands are integer types.
The constant numbers are masks (think of them in binary). So what the code does is apply the bitwise AND operator on the byte and the mask and divide by the number, in order to get the bit.
For example:
xxxxxxxx & 00000100 = 00000x000
if x == 1
00000x00 / 00000100 = 000000001
else if x == 0
00000x00 / 00000100 = 000000000
In C# use the BitArray class to directly index individual bits.
To set an individual bit i is straightforward:
b |= 1 << i;
To reset an individual bit i is a little more awkward:
b &= ~(1 << i);
Be aware that both the bitwise operators and the shift operators tend to promote everything to int which may unexpectedly require casting.
As said this is a bitwise AND, not a logical AND. I do see that this has been said quite a few times before me, but IMO the explanations are not so easy to understand.
I like to think of it like this:
Write up the binary numbers under each other (here I'm doing 5 and 1):
101
001
Now we need to turn this into a binary number, where all the 1's from the 1st number, that is also in the second one gets transfered, that is - in this case:
001
In this case we see it gives the same number as the 2nd number, in which this operation (in VB) returns true. Let's look at the other examples (using 5 as i):
(5 and 2)
101
010
----
000
(false)
(5 and 4)
101
100
---
100
(true)
(5 and 8)
0101
1000
----
0000
(false)
(5 and 16)
00101
10000
-----
00000
(false)
EDIT: and obviously I miss the entire point of the question - here's the translation to C#:
cbi[1].Checked = i & 1 == 1;
cbi[2].Checked = i & 2 == 2;
cbi[3].Checked = i & 4 == 4;
cbi[4].Checked = i & 8 == 8;
cbi[5].Checked = i & 16 == 16;
I prefer to use hexadecimal notation when bit twiddling (e.g. 0x10 instead of 16). It makes more sense as you increase your bit depths as 0x20000 is better than 131072.
Related
I want to set the last 4 bits of my byte list to 0. I've tried this line of code but it does not work:
myData = 1111.1111
myData should be = 1111.0000
(myData & 0x0F) >> 4
Assuming you mean that "4 last bits" is 4 least significant bits, I have this code example for you:
var myData = 0xFF;
var result = myData & ~0xF;
So, basically, what you want here is not to set the 4 least significant bits to 0, but to preserve the rest of the data. To achieve this you have to prepare the "passthrough" mask, which matches the criteria, this is the one's complement of the non-needed bits mask i.e. the one's complement of the 0xF (also note that 0xF = (2 to the power of 4) - 1 -- where 4 is the number of the desired cleared out LSBs). Thus, ~0xF is the desired mask -- you just have to apply it to the number -- myData & ~0xF.
N.B. The one's complement approach is better than magical numbers, pre-computed yourself (such as 0xF in the anwser above), as the compiler will generate the valid number of MSBs (most significant bits) for the type you use this approach against.
An even safer approach would be to compute the one's complement of the variable itself, giving the effective code stated below:
var myData = 0xFF;
var result = ~(~myData | 0xF);
That's it!
To preserve the 4 high bits and zero the 4 low bits
myData & 0xF0
My apologies if this has been asked/answered before but I'm honestly not even sure how to word this as a question properly. I have the following bit pattern:
0110110110110110110110110110110110110110110110110110110110110110
I'm trying to perform a shift that'll preserve my underlying pattern; my first instinct was to use right rotation ((x >> count) | (x << (-count & 63))) but the asymmetry in my bit pattern results in:
0011011011011011011011011011011011011011011011011011011011011011 <--- wrong
The problem is that the most significant (far left) bit ends up being 0 instead of the desired 1:
1011011011011011011011011011011011011011011011011011011011011011 <--- right
Is there a colloquial name for this function I'm looking for? If not, how could I go about implementing this idea?
Additional Information:
While the question is language agnostic I'm currently trying to solve this using C#.
The bit patterns I'm using are entirely predictable and always have the same structure; the pattern starts with a single zero followed by n - 1 ones (where n is an odd number) and then repeats infinitely.
I'd like to accomplish this without conditional operations since they'd defeat the purpose of using bitwise manipulation in the first place but maybe I have no choice...
You've got a number structured like this:
B16 B15 B14 B13 B12 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00
? 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0
The ? needs to appear in the MSB (B15, or B63, or whatever) after the shift. Where does it come from? Well, the closest copy is found n places to the right:
B13 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0
^--------------/
If your word has width w, this is 1 << (w-n)
*
So you can do:
var selector = 1 << (w-n);
var rotated = (val >> 1) | ((val & selector) << (n-1));
But you may want a multiple shift. Then we need to build a wider mask:
? 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0
* * * * *
Here I've chosen to pretend n = 6, it just needs to be a multiple of the basic n, and larger than shift. Now:
var selector = ((1UL << shift) - 1) << (w - n);
var rotated = (val >> shift) | ((val & selector) << (n - shift));
Working demonstration using your pattern: http://rextester.com/UWYSW47054
It's easy to see that the output has period 3, as required:
1:B6DB6DB6DB6DB6DB
2:DB6DB6DB6DB6DB6D
3:6DB6DB6DB6DB6DB6
4:B6DB6DB6DB6DB6DB
5:DB6DB6DB6DB6DB6D
6:6DB6DB6DB6DB6DB6
7:B6DB6DB6DB6DB6DB
8:DB6DB6DB6DB6DB6D
9:6DB6DB6DB6DB6DB6
10:B6DB6DB6DB6DB6DB
11:DB6DB6DB6DB6DB6D
Instead of storing a lot of repetitions of a pattern, just store one recurrence and apply modulo operations on the indexes
byte[] pattern = new byte[] { 0, 1, 1 };
// Get a "bit" at index "i", shifted right by "shift"
byte bit = pattern[(i - shift + 1000000 * byte.Length) % byte.Length];
The + 1000000 * byte.Length must be greater than the greatest expected shift and ensures that we get a posistive sum.
This allows you to store patterns of virtually any length.
An optimization would be to store a mirrored version of the pattern. You could then shift left instead of right. This would simplify the index calculation
byte bit = pattern[(i + shift) % byte.Length];
Branchless Answer after a poke by #BenVoigt:
Get the last bit b by doing (n & 1);
Return n >> 1 | b << ((sizeof(n) - 1).
Original Answer:
Get the last bit b by doing (n & 1);
If b is 1, right shift the number by 1 bit and bitwise-OR it with 1 << (sizeof(n) - 1);
If b is 0, just right shift the number by 1 bit.
The problem was changed a bit through the comments.
For all reasonable n, the following problem can be solved efficiently after minimal pre-computation:
Given an offset k, get 64 bits starting at that position in the stream of bits that follows the pattern of (zero, n-1 ones) repeating.
Clearly the pattern repeats with a period of n, so only n different ulongs have to be produced for every given value of n. That could either be done explicitly, constructing all of them in pre-processing (they could be constructed in any obvious way, it doesn't really matter since that only happens once), or left more implicitly by storing only two ulongs per value for n (this works under the assumption that n < 64, see below) and then extracting a range from them with some shifting/ORing. Either way, use offset % n to compute which pattern to retrieve (since the offset is increasing in a predictable manner, no actual modulo operation is required[1]).
Even with the first method, memory consumption will be reasonable since this optimization is only an optimization for low n: in particular for n > 64 there will be fewer than 1 zero per word on average, so the "old fashioned way" of visiting every multiple of n and resetting that bit starts to skip work while the above trick would still visit every word and would not be able anymore to reset multiple bits at once.
[1]: if there are multiple n's in play at the same time, a possible strategy is keeping an array offsets where offsets[n] = offset % n, which could be updated according to: (not tested)
int next = offsets[n] + _64modn[n]; // 64 % n precomputed
offsets[n] = next - (((n - next - 1) >> 31) & n);
The idea being that n is subtracted whenever next >= n. Only one subtraction is needed since the offset and thing added to the offset are already reduced modulo n.
This offset-increment can be done with System.Numerics.Vectors, which is very feature-poor compared to actual hardware but is just about able to do this. It can't do the shift (yes, it's weird) but it can implement a comparison in a branchless way.
Doing one pass per value of n is easier, but touches lots of memory in a cache unfriendly manner. Doing lots of different n at the same time may not be great either. I guess you'd just have to bechmark that..
Also you could consider hard-coding it for some low numbers, something like offset % 3 is fairly efficient (unlike offset % variable). This does take manual loop-unrolling which is a bit annoying, but it's actually simpler, just big in terms of lines of code.
Say you have two hashes H(A) and H(B) and you want to combine them. I've read that a good way to combine two hashes is to XOR them, e.g. XOR( H(A), H(B) ).
The best explanation I've found is touched briefly here on these hash function guidelines:
XORing two numbers with roughly random distribution results in another number still with roughly random distribution*, but which now depends on the two values.
...
* At each bit of the two numbers to combine, a 0 is output if the two bits are equal, else a 1. In other words, in 50% of the combinations, a 1 will be output. So if the two input bits each have a roughly 50-50 chance of being 0 or 1, then so too will the output bit.
Can you explain the intuition and/or mathematics behind why XOR should be the default operation for combining hash functions (rather than OR or AND etc.)?
xor is a dangerous default function to use when hashing. It is better than and and or, but that doesn't say much.
xor is symmetric, so the order of the elements is lost. So "bad" will hash combine the same as "dab".
xor maps pairwise identical values to zero, and you should avoid mapping "common" values to zero:
So (a,a) gets mapped to 0, and (b,b) also gets mapped to 0. As such pairs are almost always more common than randomness might imply, you end up with far to many collisions at zero than you should.
With these two problems, xor ends up being a hash combiner that looks half decent on the surface, but not after further inspection.
On modern hardware, adding usually about as fast as xor (it probably uses more power to pull this off, admittedly). Adding's truth table is similar to xor on the bit in question, but it also sends a bit to the next bit over when both values are 1. This means it erases less information.
So hash(a) + hash(b) is better than hash(a) xor hash(b) in that if a==b, the result is hash(a)<<1 instead of 0.
This remains symmetric; so the "bad" and "dab" getting the same result remains a problem. We can break this symmetry for a modest cost:
hash(a)<<1 + hash(a) + hash(b)
aka hash(a)*3 + hash(b). (calculating hash(a) once and storing is advised if you use the shift solution). Any odd constant instead of 3 will bijectively map a "k-bit" unsigned integer to itself, as map on unsigned integers is math modulo 2^k for some k, and any odd constant is relatively prime to 2^k.
For an even fancier version, we can examine boost::hash_combine, which is effectively:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
here we add together some shifted versions of lhs with a constant (which is basically random 0s and 1s – in particular it is the inverse of the golden ratio as a 32 bit fixed point fraction) with some addition and an xor. This breaks symmetry, and introduces some "noise" if the incoming hashed values are poor (ie, imagine every component hashes to 0 – the above handles it well, generating a smear of 1 and 0s after each combine. My naive 3*hash(a)+hash(b) simply outputs a 0 in that case).
Extending this to 64 bits (using the expansion of pi as our constant for 64 bits, as it is odd at 64 bits):
size_t hash_combine( size_t lhs, size_t rhs ) {
if constexpr (sizeof(size_t) >= 8) {
lhs ^= rhs + 0x517cc1b727220a95 + (lhs << 6) + (lhs >> 2);
} else {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
}
return lhs;
}
(For those not familiar with C/C++, a size_t is an unsigned integer value which is big enough to describe the size of any object in memory. On a 64 bit system, it is usually a 64 bit unsigned integer. On a 32 bit system, a 32 bit unsigned integer.)
Assuming uniformly random (1-bit) inputs, the AND function output probability distribution is 75% 0 and 25% 1. Conversely, OR is 25% 0 and 75% 1.
The XOR function is 50% 0 and 50% 1, therefore it is good for combining uniform probability distributions.
This can be seen by writing out truth tables:
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
a | b | a OR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
Exercise: How many logical functions of two 1-bit inputs a and b have this uniform output distribution? Why is XOR the most suitable for the purpose stated in your question?
In spite of its handy bit-mixing properties, XOR is not a good way to combine hashes due to its commutativity. Consider what would happen if you stored the permutations of {1, 2, …, 10} in a hash table of 10-tuples.
A much better choice is m * H(A) + H(B), where m is a large odd number.
Credit: The above combiner was a tip from Bob Jenkins.
Xor may be the "default" way to combine hashes but Greg Hewgill's answer also shows why it has its pitfalls:
The xor of two identical hash values is zero.
In real life, there are identical hashes are more common than one might have expected. You might then find that in these (not so infrequent) corner cases, the resulting combined hashes are always the same (zero). Hash collisions would be much, much more frequent than you expect.
In a contrived example, you might be combining hashed passwords of users from different websites you manage. Unfortunately, a large number of users reuse their passwords, and a surprising proportion of the resulting hashes are zero!
There's something I want to explicitly point out for others who find this page. AND and OR restrict output like BlueRaja - Danny Pflughoe is trying to point out, but can be better defined:
First I want to define two simple functions I'll use to explain this: Min() and Max().
Min(A, B) will return the value that is smaller between A and B, for example: Min(1, 5) returns 1.
Max(A, B) will return the value that is larger between A and B, for example: Max(1, 5) returns 5.
If you are given: C = A AND B
Then you can find that C <= Min(A, B) We know this because there is nothing you can AND with the 0 bits of A or B to make them 1s. So every zero bit stays a zero bit and every one bit has a chance to become a zero bit (and thus a smaller value).
With: C = A OR B
The opposite is true: C >= Max(A, B) With this, we see the corollary to the AND function. Any bit that is already a one cannot be ORed into being a zero, so it stays a one, but every zero bit has a chance to become a one, and thus a larger number.
This implies that the state of the input applies restrictions on the output. If you AND anything with 90, you know the output will be equal to or less than 90 regardless what the other value is.
For XOR, there is no implied restriction based on the inputs. There are special cases where you can find that if you XOR a byte with 255 than you get the inverse but any possible byte can be output from that. Every bit has a chance to change state depending on the same bit in the other operand.
If you XOR a random input with a biased input, the output is random. The same is not true for AND or OR. Example:
00101001 XOR 00000000 = 00101001
00101001 AND 00000000 = 00000000
00101001 OR 11111111 = 11111111
As #Greg Hewgill mentions, even if both inputs are random, using AND or OR will result in biased output.
The reason we use XOR over something more complex is that, well, there's no need: XOR works perfectly, and it's blazingly stupid-fast.
Cover the left 2 columns and try to work out what the inputs are using just the output.
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
When you saw a 1-bit you should have worked out that both inputs were 1.
Now do the same for XOR
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
XOR gives away nothing about it inputs.
XOR does not ignore some of the inputs sometimes like OR and AND.
If you take AND(X, Y) for example, and feed input X with false, then the input Y does not matter...and one probably would want the input to matter when combining hashes.
If you take XOR(X, Y) then BOTH inputs ALWAYS matter. There would be no value of X where Y does not matter. If either X or Y is changed then the output will reflect that.
The source code for various versions of hashCode() in java.util.Arrays is a great reference for solid, general use hashing algorithms. They are easily understood and translated into other programming languages.
Roughly speaking, most multi-attribute hashCode() implementations follow this pattern:
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
You can search other StackOverflow Q&As for more information about the magic behind 31, and why Java code uses it so frequently. It is imperfect, but has very good general performance characteristics.
I'm writing some performance-sensitive C# code that deals with character comparisons. I recently discovered a trick where you can tell if a char is equal to one or more others without branching, if the difference between them is a power of 2.
For example, say you want to check if a char is U+0020 (space) or U+00A0 (non-breaking space). Since the difference between the two is 0x80, you can do this:
public static bool Is20OrA0(char c) => (c | 0x80) == 0xA0;
as opposed to this naive implementation, which would add an additional branch if the character was not a space:
public static bool Is20OrA0(char c) => c == 0x20 || c == 0xA0;
How the first one works is since the difference between the two chars is a power of 2, it has exactly one bit set. So that means when you OR it with the character and it leads to a certain result, there are exactly 2 ^ 1 different characters that could have lead to that result.
Anyway, my question is, can this trick somehow be extended to characters with differences that aren't multiples of 2? For example, if I had the characters # and 0 (which have a difference of 13, by the way), is there any sort of bit-twiddling hack I could use to check if a char was equal to either of them, without branching?
Thanks for your help.
edit: For reference, here is where I first stumbled across this trick in the .NET Framework source code, in char.IsLetter. They take advantage of the fact that a - A == 97 - 65 == 32, and simply OR it with 0x20 to uppercase the char (as opposed to calling ToUpper).
If you can tolerate a multiply instead of a branch, and the values you are testing against only occupy the lower bits of the data type you are using (and therefore won't overflow when multiplied by a smallish constant, consider casting to a larger data type and using a correspondingly larger mask value if this is an issue), then you could multiply the value by a constant to force the two values to be a power of 2 apart.
For example, in the case of # and 0 (decimal values 35 and 48), the values are 13 apart. Rounding down, the nearest power of 2 to 13 is 8, which is 0.615384615 of 13. Multiplying this by 256 and rounding up, to give an 8.8 fixed point value gives 158.
Here are the binary values for 35 and 48, multiplied by 158, and their neighbours:
34 * 158 = 5372 = 0001 0100 1111 1100
35 * 158 = 5530 = 0001 0101 1001 1010
36 * 158 = 5688 = 0001 0110 0011 1000
47 * 158 = 7426 = 0001 1101 0000 0010
48 * 158 = 7548 = 0001 1101 1010 0000
49 * 158 = 7742 = 0001 1110 0011 1110
The lower 7 bits can be ignored because they aren't necessary in order to separate any of the neighbouring values from each other, and apart from that, the values 5530 and 7548 only differ in bit 11, so you can use the mask and compare technique, but using an AND instead of an OR. The mask value in binary is 1111 0111 1000 0000 (63360) and the compare value is 0001 0101 1000 0000 (5504), so you can use this code:
public static bool Is23Or30(char c) => ((c * 158) & 63360) == 5504;
I haven't profiled this, so I can't promise it's faster than a simple compare.
If you do implement something like this, be sure to write some test code that loops through every possible value that can be passed to the function, to verify that it works as expected.
You can use the same trick to compare against a set of 2^N values provided that they have all other bits equal except N bits. E.g if the set of values is 0x01, 0x03, 0x81, 0x83 then N=2 and you can use (c | 0x82) == 0x83. Note that the values in the set differ only in bits 1 and/or 7. All other bits are equal. There are not many cases where this kind of optimization can be applied, but when it can and every little bit of extra speed counts, its a good optimization.
This is the same way boolean expressions are optimized (e.g. when compiling VHDL). You may also want to look up Karnaugh maps.
That being said, it is really bad practice to do this kind of comparisons on character values especially with Unicode, unless you know what you are doing and are doing really low level stuff (such as drivers, kernel code etc). Comparing characters (as opposed to bytes) has to take into account the linguistic features (such as uppercase/lowercase, ligatures, accents, composited characters etc)
On the other hand if all you need is binary comparison (or classification) you can use lookup tables. With single byte character sets these can be reasonably small and really fast.
If not having branches is really your main concern, you can do something like this:
if ( (x-c0|c0-x) & (x-c1|c1-x) & ... & (x-cn|cn-x) & 0x80) {
// x is not equal to any ci
If x is not equal to a specific c, either x-c or c-x will be negative, so x-c|c-x will have bit 7 set. This should work for signed and unsigned chars alike. If you & it for all c's, the result will have bit 7 set only if it's set for every c (i.e. x is not equal to any of them)
I understand that the single ampersand operator is normally used for a 'bitwise AND' operation. However, can anyone help explain the interesting results you get when you use it for comparison between two numbers?
For example;
(6 & 2) = 2
(10 & 5) = 0
(20 & 25) = 16
(123 & 20) = 16
I'm not seeing any logical link between these results and I can only find information on comparing booleans or single bits.
Compare the binary representations of each of those.
110 & 010 = 010
1010 & 0101 = 0000
10100 & 11001 = 10000
1111011 & 0010100 = 0010000
In each case, a digit is 1 in the result only when it is 1 on both the left AND right side of the input.
You need to convert your numbers to binary representation and then you will see the link between results like 6 & 2= 2 is actually 110 & 010 =010 etc
10 & 5 is 1010 & 0101 = 0000
The binary and operation is performed on the integers, represented in binary. For example
110 (6)
010 (2)
--------
010 (2)
The bitwise AND is does exactly that: it does an AND operation on the Bits.
So to anticipate the result you need to look at the bits, not the numbers.
AND gives you 1, only if there's 1 in both number in the same position:
6(110) & 2(010) = 2(010)
10(1010) & 5(0101) = 0(0000)
A bitwise OR will give you 1 if there's 1 in either numbers in the same position:
6(110) | 2(010) = 6(110)
10(1010) | 5(0101) = 15(1111)
6 = 0110
2 = 0010
6 & 2 = 0010
20 = 10100
25 = 11001
20 & 25 = 10000
(looks like you're calculation is wrong for this one)
Etc...
Internally, Integers are stored in binary format. I strongly suggest you read about that. Knowing about the bitwise representation of numbers is very important.
That being said, the bitwise comparison compares the bits of the parameters:
Decimal: 6 & 2 = 2
Binary: 0110 & 0010 = 0010
Bitwize AND matches the bits in binary notation one by one and the result is the bits that are comon between the two numbers.
To convert a number to binary you need to understand the binary system.
For example
6 = 110 binary
The 110 represents 1x4 + 1x2 + 0x1 = 6.
2 then is
0x4 + 1x2 + 0x1 = 2.
Bitwize and only retains the positions where both numbers have the position set, in this case the bit for 2 and the result is then 2.
Every extra bit is double the last so a 4 bit number uses the multipliers 8, 4, 2, 1 and can there fore represent all numbers from 0 to 15 (the sum of the multipliers.)