xor with 3 values

16,429

Solution 1

((true ^ true) ^ true) will return true, which is not what you would expect from true ^ true ^ true.

To make sure that you get the outcome you want (only one value to be true) do the following:

if ((a && !b && !c) || (!a && b && !c) || (!a && !b && c))

Alternatively, based on @jcomeau_ictx answer, you can do the following:

if( Convert.ToInt32(a) + Convert.ToInt32(b) + Convert.ToInt32(c) == 1 )

Or, you could create a function:

public bool TernaryXor(bool a, bool b, bool c)
{
    //return ((a && !b && !c) || (!a && b && !c) || (!a && !b && c));

    // taking into account Jim Mischel's comment, a faster solution would be:
    return (!a && (b ^ c)) || (a && !(b || c));
}

EDIT: You might want to name the function TernaryXor so that it is more clear as to the outcome of the function.

Solution 2

One way would be to convert the Boolean values to an integer, add the results, and compare to 1.

Solution 3

Because I can't get enough Linq, how about:

new[] { a, b, c }.Count(v => v) == 1

Solution 4

this is short !(a&&b&&c) && (a^b^c)

Solution 5

It's a tricky one, for sure. Given what you want:

a b c rslt
0 0 0  0
0 0 1  1
0 1 0  1
0 1 1  0
1 0 0  1
1 0 1  0
1 1 0  0
1 1 1  0

This will do it:

rslt = (!a & (b ^ c)) || (a & !(b | c));

The first part handles the four cases where a is 0. The second part, where a is not 0.

A simpler way to look at it is this:

rslt = (a | b | c) & !((a & b) | (a & c) | (b & c))

That is, one of the three must be true, but no two (or more) can be true.

It seems like there should be a way to simplify further, but it's not coming to mind. Maybe I need more caffeine.

EDIT

I think this one is the solution I was looking for this morning:

rslt = a ? !(b | c) : (b ^ c);

Now, as to why I used | instead of ||:

It's a combination of a style issue and an old bias against branching (old habits die hard). !(b | c) generates this IL code:

ldarg.1
ldarg.2
or
ldc.i4.0
ceq
stloc.0

There aren't any branches in that code. If I use ||, as in !(b ||c), it generates:

  ldarg.1
  brfalse.s IL_009B
  ldarg.2
  br.s IL_009C
IL_009B:
  ldc.i4.1
IL_009C:
  stloc.0

Which has two branches. I don't know if the JIT-produced code will mirror that, but I suspect it will. So one bit of code is 6 instructions that are always executed. The other is 6 instructions of which sometimes only 4 are executed. But branching penalties might very well eat up any gains from not executing two of the instructions.

I realize that modern CPUs are much better at branching than the 8086 was, and there might not be any detectable difference in the runtime of these two code snippets. Even if there were, it's unlikely that it would make a significant difference in the overall runtime of the programs I typically write.

But I tell you that it certainly used to! On the 8086, where branching was very expensive, the difference between (b | c) and (b || c) was huge.

Finally, using |, as you noted, forces evaluation of the entire expression. My original code says, in effect, "if this expression is true or that expression is true." Using && and || turns it into a bunch of conditionals and is, in my brain, more difficult to read.

So: an old prejudice based on most likely outdated performance considerations. But harmless.

One has to be careful, though, not to write something like (b() | c()) unless both functions must be evaluated.

Share:
16,429
sianabanana
Author by

sianabanana

Updated on June 02, 2022

Comments

  • sianabanana
    sianabanana almost 2 years

    I need to do an xor conditional between 3 values, ie i need one of the three values to be true but not more than one and not none.

    I thought i could use the xor ^ operator for this but its not working as expected.

    I expected that this would return false but it doesnt. (true ^ true ^ true)

    all other combinations seem to work as i expected.

    When looking at the docs for the xor operator they only talk about comparing 2 values and i cant find anything on doing this for 3 or more values online.

    Can anyone shed any light or suggest a simple way of doing this?