Search code examples
c#operatorslogical-operators

What does &= do in C# when dealing with numbers?


I'm trying to learn C# right now and I'm using W3schools (website) in order to do so, but I came across some operators I dont completely understand.

I know that there are many questions asking what &= does in C# but I couldn't find anything relevant to my issue (dealing with numbers rather than True or False values)

From what I've gathered from online, the && operator is just an AND operator, and the & operator is just an AND operator but all conditions are checked But when I searched for &= I can't find anything relevant to the way that its used on the W3Schools website, it shows the &= operator in use with numbers rather than boolean, and on the section that lets you try it out, I was receiving an output that I couldn't understand.

This is the link to the website page:

https://www.w3schools.com/cs/cs_operators_assignment.php

This is the link to the 'try it out' section where I got the code:

https://www.w3schools.com/cs/trycs.php?filename=demo_oper_ass7

Here's the code:

int x = 5;
x &= 3;
Console.WriteLine(x);  

When I leave it as shown in the code above, I get an output of 1

When x = 10, output is 2

When x = 15, output is 3

When x = 20, output is 0

When x = 4329, output is 1

etc...

Please can somebody explain the &= operator, and if possible, the |=, ^= operators too? I understand the use of all these operators by themselves when I searched them up however those understandings dont match with the usage of the ...= version shown on the website

Thanks alot


Solution

  • Let me preface this with the fact that I technically don't know C#, but I do know C, C++, and Java, and I also know that C# is a language in the C/C++ family (just like Java), so I would literally bet my life on this being correct in any and all of the languages mentioned.

    Generally, for any binary operator _, the expression "a _= b" is (supposed to be considered -- C++ kind of messes with this, but that's besides the point) equivalent to "a = a _ b". As such,

    • a &= b is a = a & b
    • a |= b is a = a | b
    • a ^= b is a = a ^ b

    All of those operators (&, |, ^) are, as you correctly recognized, boolean (as in, concerning only "true" and "false") operations. However, they are bitwise (as in binary numbers) boolean operations. In particular, the difference between the "logical" (as they are generally called, although the term is fairly misleading) operators and the "bitwise" operators is that the "logical" versions consider the veracity ("true-or-false-ness") of their operands as a whole, whereas the "bitwise" (remember that "bit" is short for "binary digit") versions consider each bit -- i.e. each digit of their operand when they are written in the binary system (Wiki "Positional notation" and "Binary number" for more information). Thus, && and || (there is no "logical" version of ^ in any C-like language I know) work on truth values as a whole -- which, in the case of Java and (almost certainly) C#, means type boolean/bool. For example, operator && indicates logical AND:

    a b a && b
    true true true
    true false false
    false true false
    false false false

    Analogously, operator || indicates logical OR. The bitwise operations, however, consider each binary digit of their operands (and since "binary" basically means "having two", this equates the whole "ones and zeroes" thing you see/hear everywhere), where (predictably) "0" indicates "false" and "1" indicates "true". Ergo, you can use the table above for "a & b" if only you replace all the "false" with "0" and all the "true" with "1". This explains why your program outputs what it does: In the initial example, you print 5 & 3, which, in binary notation, is 101 & 011. Ergo, in the result, only the digits where both operands have one (namely the last place) will be "1". Observe:

       101
    &  011
    == 001
    

    (If the binary notation is an issue, 5 = 22 + 20 = 4 + 1, and 3 = 21 + 20 = 2 + 1.) Since "1 AND 0" is "0" (leftmost digit), "0 AND 1" is "0" (middle digit), and "1 AND 1" is "1" (rightmost digit).

    If it helps understand, consider the program as a mathematician would: Argue that any variable can have only one definition, and thus only one value. Thus, when you assign a new value, the system would actually have to intoduce a "new" ("hidden") variable for the new definition, so your

    x = 5
    x = x & 3
    print x
    

    is actually

    x0 = 5
    x1 = x0 & 3
    print x1   // not a math thing, just here for completeness
    

    (This is actually how the compiler views your program; Wiki "Static single-assignment form" if interested.)

    The bottom line is: If you have a number ("more than one") of boolean values (true or false), you can combine them into an integer value (byte/sbyte/short/ushort/int/uint/long/ulong) by assigning one binary place for each boolean value and then use boolean operations (& → AND, | → OR, ^ → XOR) to combine all of the boolean values at once. Consider (and note again that I don't actually know C# per se, so I'm pretty much winging this based on some Googling; there might be some issues, but I trust the principle becomes clear) the definitions:

    static readonly int CHEAP = 0;     // binary: 000
    static readonly int EXPENSIVE = 1; // binary: 001
    static readonly int LIGHT = 0;     // binary: 000
    static readonly int HEAVY = 2;     // binary: 010
    static readonly int WEAK = 0;      // binary: 000
    static readonly int POWERFUL = 4;  // binary: 100
    // and now, the combinations:
    static readonly int GENERIC = 0;   // binary: 000 (cheap, light, weak)
    static readonly int LUXURY = 3;    // binary: 011 (expensive, heavy, ?)
    static readonly int MUSCLE = 7;    // binary: 111 (expensive, heavy, powerful)
    static readonly int PONY = 6;      // binary: 011 (cheap, heavy, powerful)
    

    Here, we consider the following veracities:

    • The 20 (i.e. rightmost binary) place indicates "expensive".
    • The 21 (i.e. middle binary) place indicates "heavy".
    • The 22 (i.e. leftmost binary) place indicates "powerful".

    Now, we can put all three boolean values into an integer (would fit into a byte, but usually people use int):

    class Car {
        readonly string name;
        readonly int traits;
        Car(string name, int traits) {
            this.name = name;
            this.traits = traits;
        }
        bool isGeneric() {
            // if and only if all veracities are "false"
            // should be the same as "return traits == GENERIC", but somebody might set traits to > 7
            // note that EXPENSIVE | HEAVY | POWERFUL == 7, so we only consider the three "defined" bools
            return (traits & (EXPENSIVE | HEAVY | POWERFUL)) == GENERIC;
        }
        bool isLuxury() {
            // if and only if the two veracities dictated by LUXURY match, with no regard for power
            return (traits & LUXURY) == LUXURY;
        }
        bool isMuscle() {
            // if and only if all veracities are "true"
            // should be the same as "return traits == MUSCLE", but somebody might set traits to > 7
            return (traits & MUSCLE) == MUSCLE;
        }
        bool isPony() {
            // if and only if all three veracities dictated by PONY match
            // note that EXPENSIVE | HEAVY | POWERFUL == 7, so we only consider the three "defined" bools
            // also note that this requires that EXPENSIVE not be set,
            // i.e. "== PONY" is equivalent to "== (HEAVY | POWERFUL)" and "== (CHEAP | HEAVY | POWERFUL)"
            return (traits & (EXPENSIVE | HEAVY | POWERFUL)) == PONY;
        }
    }
    

    Then, we can do something like:

    // "PONY" could also be written as "CHEAP | HEAVY | POWERFUL" or "HEAVY | POWERFUL"
    Car fordMustang = new Car("Ford Mustang", PONY);
    

    (And yes, I drive a Mustang. :P)