Metamorphing Machine I rather be this walking metamorphosis
than having that old formed opinion about everything!

Not, And, Or, Xor, Imp, Eqv, and Null. Oh, my!

Visual Basic have among its operators two unusual ones: Imp and Eqv 1.
Also, what's the reasoning behind all these operators' truth tables when dealing with Null?
To talk about it, we'll need to talk about bitwise operators, logical operators, two's complement representation, and a bit of VB's history. Phew!

How True came to be -1

Believe it or not, VB didn't have a boolean data type until its 4th version.
So, what VB developers (and QuickBASIC ones before them) did on those dark times?

They would define a FALSE constant having a value of zero:
Const FALSE = 0%

And then they would define a TRUE constant the most logical way:
Const TRUE = Not FALSE

See, that FALSE constant was an integer, with all its bits set to zero.
When you Not it, you flip all those bits from zero to one. So now we have an integer constant TRUE that happens to be -1.
How is that?

Encoding integers

Computers have finite memory, so they are unable to cope with infinite numbers.
Being so, they constrain numbers to some size, being 8, 16, 32, or 64 bits the most common ones.

Now suppose you want to represent negative numbers using these constrained integers. One solution would be to use the most significant bit (MSB) to represent the sign.
If we have a zero there, the number is positive, otherwise, it is negative.
To understand it better, let's do an exercise with four-bit numbers:

0000 = 0
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7

Now, if we just slap a 1 at the MSB to make them negative, how would they look like?

1000 = -0
1001 = -1
1010 = -2
1011 = -3
1100 = -4
1101 = -5
1110 = -6
1111 = -7

The first thing we notice is that now we have a negative zero. Hmm... Not good.
But taking that aside for now, how would we use these numbers in addition, for instance?

-2 (1010)
+3 (0011)

+1 (0001)

How in the world would we start with 1010 and 0011 and end up with 0001? I don't know how to calculate it.
That's why the most common way to represent negative integer numbers in computers is using two's complement representation.

Converting from positive to negative - and back - the two's complement way

The rules are simple: To convert positive numbers to negative ones or vice-versa, we change zeros to ones, ones to zeros and then we add one to it.
But beware: When adding one to one in binary, we got zero and carry one to the next addition. It's like when you add five to five and get zero, carrying one to the next addition.

Let's see how it goes for zero:
0000 → flip bits → 1111 → add one → 10000 → discard the fifth bit because we are dealing with four-bit integers → 0000
This is good. Minus zero is zero. Check.

Now let's see what happens to, say, two:
0010 → flip bits → 1101 → add one → 1110

And let's try again that addition with our new -2:

-2 (1110)
+3 (0011)

+1 (10001)

Discarding the fifth bit, we got 0001. Pretty good!
So, using two's complement, we end up with this table:

0000 = 0
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = -8
1001 = -7
1010 = -6
1011 = -5
1100 = -4
1101 = -3
1110 = -2
1111 = -1

And now you can see why an integer with all its bits set to one is equal to -1.
Even though we are using simple four-bit integers in our reasoning, -1 in 8, 16, 32, or 64 bits is always represented as a stream of 1s.

When VB got a proper boolean data type, this convention was adopted, so converting True to integer would yield -1 and peace was on Earth.
This is in contrast to other programming languages, like C, for instance, that takes zero as being false, but one as being true.
(Actually, both VB and C consider zero to be false and anything different from zero to be true. But the canonical value for true is -1 in VB and 1 in C.)

How does C negate false (meaning: zero) and get 1 as true?
Because it has bitwise operators and logical operators.
Bitwise operators operate on the bits of an integer. As you just saw in the long explanation above, VB operators are bitwise, and so is C's ~ not, & and, | or, and ^ xor operators.

C's logical operators (! not, && and, and || or) do the magic in transforming 0 to 1 and vice-versa whenever needed.
Please, note that VB does not have logical operators. Also, there is one more difference between C's and VB's operators. C's ones are short-circuited.

Short what?

Suppose we have the following code fragment:

If Hour(Time) >= 6 And Hour(Time) < 12 Then
MsgBox "Good morning!"
Else (...)

If the current hour is greater than or equal to six, we need to check if it is also lower than twelve, so we can display the morning greeting.
But if the current hour is not greater than or equal to six, we don't need to check the second condition. It is not morning. We can go directly to the else part.
That's what short-circuited operators do. They don't care for the second condition when the first one alone is enough to know what to do next.

With the And operator, if the first expression is false, we don't need to check the second one. False and whatever will always be false.
As for the Or operator, if the first expression is true, we don't need to check the second one either. True or whatever will always be true.

VB does not have short-circuit operators, so why did I bring it to the table?
Because short-circuiting allow us to reason about VB's operators and Null.
But before we take a look at the truth tables for the operators, what about Imp and Eqv. What's there are good for?

Eqv and Imp

Eqv, or equivalence, is the easiest one to explain.
Every time both bits are equal, we got a one. Every time they are different, we got a zero.

Imp, or implication, is the trickiest one.
The best explanation I saw was about how a promise holds.
Imagine a parent say the following to their child:
"If you mow the grass, I'll give you one buck."
We can think of it as "Child trims the grass implies they receive a buck."

Now we have four possibilities:
For the first situation, that parent held their promise, so the result is True. True Imp True = True
For the second, the promise was broken: True Imp False = False
For the third, we can naively think that the promise was fulfilled. False Imp False = True.
For the last one, here comes the trick. The parent did not say what would happen it the grass was not mowed. There is no "else" clause there.
In theory, anything could happen. They could have not given the money, or they could have grounded the child. Who knows?
So, we can say that the promise holds because any outcome is a "legal" one.

Once we have that cleared, here are the truth tables for VB's operators:

AND True False Null
True True False
False False False
Null Null
 
OR True False Null
True True True
False True False
Null Null
 
XOR True False Null
True False True
False True False
Null Null
 
IMP True False Null
True True False
False True True
Null Null
 
EQV True False Null
True True False
False False True
Null Null

Now, to fill up the Null blanks, every time you can short-circuit the evaluation, write the answer there. Every time you cannot, write NULL instead:

AND True False Null
True True False Null
False False False False
Null Null False Null
 
OR True False Null
True True True True
False True False Null
Null True Null Null
 
XOR True False Null
True False True Null
False True False Null
Null Null Null Null
 
IMP True False Null
True True False Null
False True True True
Null True Null Null
 
EQV True False Null
True True False Null
False False True Null
Null Null Null Null

Probably you had a hard time with the Imp table. I know I had.
It helps to think that A Imp B is the same as Not A Or B.

Andrej Biasic
2020-04-01
Update:
Here it is the count above being done step by step:

Adding 1110 to 0011 step by step

2021-09-01

1 Keep in mind that VB here stands for the classical VB. We are not talking about VB.NET.