Off-Topic Discussion > Programming

Tricky Bools!

(1/1)

z64555:
We've been doing a bit of maintenance with the flags system in FSO, and something interesting came up that made me stop and think.

Specifically, this:

--- Code: ---#define VM_PADLOCK_UP    (1 << 7)
#define VM_PADLOCK_REAR  (1 << 8)
#define VM_PADLOCK_LEFT  (1 << 9)
#define VM_PADLOCK_RIGHT (1 << 10)

int Viewer_mode;

// Version A
if (Viewer_mode & ~VM_PADLOCK_ANY) {}

// Version B
if (~Viewer_mode & VM_PADLOCK_ANY) {}

--- End code ---

Note versions A and B, which differ only by the location of operator~, which is the bitwise NOT operator (makes all 0's into 1's and 1's into 0's). When placed in an if{} statement, C and C++ regards a non-boolean value (ints, floats, etc.) as TRUE if any bits in the value are 1's (that is, if the value is nonzero, it is TRUE). This little quirk will make things difficult for us as you'll soon see.

--- Code: ---// Version A
LHS  RHS
00 & ~00 -> 00 & 11 -> 00 -> FALSE  // A right-hand operand of 00 makes no sense for flag checking, so we don't use it
01 & ~00 -> 01 & 11 -> 01 -> TRUE
10 & ~00 -> 10 & 11 -> 10 -> TRUE
11 & ~00 -> 11 & 11 -> 11 -> TRUE

00 & ~01 -> 00 & 10 -> 00 -> FALSE
01 & ~01 -> 01 & 10 -> 00 -> FALSE
10 & ~01 -> 10 & 10 -> 10 -> TRUE
11 & ~01 -> 11 & 10 -> 10 -> TRUE

00 & ~10 -> 00 & 01 -> 00 -> FALSE
01 & ~10 -> 01 & 01 -> 01 -> TRUE
10 & ~10 -> 10 & 01 -> 00 -> FALSE
11 & ~10 -> 11 & 01 -> 01 -> TRUE

00 & ~11 -> 00 & 00 -> 00 -> FALSE
01 & ~11 -> 01 & 00 -> 00 -> FALSE
10 & ~11 -> 10 & 00 -> 00 -> FALSE
11 & ~11 -> 11 & 00 -> 00 -> FALSE

//Version B
LHS  RHS
~00 & 00 -> 11 & 00 -> 00 -> FALSE
~01 & 00 -> 10 & 00 -> 00 -> FALSE
~10 & 00 -> 01 & 00 -> 00 -> FALSE
~11 & 00 -> 00 & 00 -> 00 -> FALSE

~00 & 01 -> 11 & 01 -> 01 -> TRUE
~01 & 01 -> 10 & 01 -> 00 -> FALSE
~10 & 01 -> 01 & 01 -> 01 -> TRUE
~11 & 01 -> 00 & 01 -> 00 -> FALSE

~00 & 10 -> 11 & 10 -> 10 -> TRUE
~01 & 10 -> 10 & 10 -> 10 -> TRUE
~10 & 10 -> 01 & 10 -> 00 -> FALSE
~11 & 10 -> 00 & 10 -> 00 -> FALSE

~00 & 11 -> 11 & 11 -> 11 -> TRUE
~01 & 11 -> 10 & 11 -> 10 -> TRUE
~10 & 11 -> 01 & 11 -> 01 -> TRUE
~11 & 11 -> 00 & 11 -> 00 -> FALSE

--- End code ---

As you can see from the snippet, the two versions are not the same.

Version A seems to check if any of the flags that are 0's in the RHS operand are 1's in the LHS.

Version B seems to check if any flags that are 1's in the RHS operand are 0's in the LHS.

Let's toss in another bit to see if these general rules hold up:

--- Code: ---// Version A
000 & ~001 -> 000 & 110 -> 000 -> FALSE
001 & ~001 -> 001 & 110 -> 000 -> FALSE
010 & ~001 -> 010 & 110 -> 010 -> TRUE
011 & ~001 -> 011 & 110 -> 010 -> TRUE
100 & ~001 -> 100 & 110 -> 100 -> TRUE
101 & ~001 -> 101 & 110 -> 100 -> TRUE
110 & ~001 -> 110 & 110 -> 110 -> TRUE
111 & ~001 -> 111 & 110 -> 110 -> TRUE

000 & ~010 -> 000 & 101 -> 000 -> FALSE
001 & ~010 -> 001 & 101 -> 001 -> TRUE
010 & ~010 -> 010 & 101 -> 000 -> FALSE
011 & ~010 -> 011 & 101 -> 001 -> TRUE
100 & ~010 -> 100 & 101 -> 100 -> TRUE
101 & ~010 -> 101 & 101 -> 101 -> TRUE
110 & ~010 -> 110 & 101 -> 100 -> TRUE
111 & ~010 -> 111 & 101 -> 101 -> TRUE

000 & ~011 -> 000 & 100 -> 000 -> FALSE
001 & ~011 -> 001 & 100 -> 000 -> FALSE
010 & ~011 -> 010 & 100 -> 000 -> FALSE
011 & ~011 -> 011 & 100 -> 000 -> FALSE
100 & ~011 -> 100 & 100 -> 100 -> TRUE
101 & ~011 -> 101 & 100 -> 100 -> TRUE
110 & ~011 -> 110 & 100 -> 100 -> TRUE
111 & ~011 -> 111 & 100 -> 100 -> TRUE

000 & ~111 -> 000 & 000-> 000 -> FALSE
001 & ~111 -> 001 & 000-> 000 -> FALSE
010 & ~111 -> FALSE
011 & ~111 -> FALSE
100 & ~111 -> FALSE
101 & ~111 -> FALSE
110 & ~111 -> FALSE
111 & ~111 -> FALSE

//Version B
~000 & 001 -> 111 & 001 -> 001 -> TRUE
~001 & 001 -> 110 & 001 -> 000 -> FALSE
~010 & 001 -> 101 & 001 -> 001 -> TRUE
~011 & 001 -> 100 & 001 -> 000 -> FALSE
~100 & 001 -> 011 & 001 -> 001 -> TRUE
~101 & 001 -> 010 & 001 -> 000 -> FALSE
~110 & 001 -> 001 & 001 -> 001 -> TRUE
~111 & 001 -> 000 & 001 -> 000 -> FALSE

~000 & 010 -> 111 & 010 -> 010 -> TRUE
~001 & 010 -> 110 & 010 -> 010 -> TRUE
~010 & 010 -> 101 & 010 -> 000 -> FALSE
~011 & 010 -> 100 & 010 -> 000 -> FALSE
~100 & 010 -> 011 & 010 -> 010 -> TRUE
~101 & 010 -> 010 & 010 -> 010 -> TRUE
~110 & 010 -> 001 & 010 -> 000 -> FALSE
~111 & 010 -> 000 & 010 -> 000 -> FALSE

~000 & 011 -> 111 & 011 -> 011 -> TRUE
~001 & 011 -> 110 & 011 -> 010 -> TRUE
~010 & 011 -> 101 & 011 -> 001 -> TRUE
~011 & 011 -> 100 & 011 -> 000 -> FALSE
~100 & 011 -> 011 & 011 -> 011 -> TRUE
~101 & 011 -> 010 & 011 -> 010 -> TRUE
~110 & 011 -> 001 & 011 -> 001 -> TRUE
~111 & 011 -> 000 & 011 -> 000 -> FALSE

~100 & 111 -> 111 & 111 -> 111 -> TRUE
~001 & 111 -> 110 & 111 -> 110 -> TRUE
~010 & 111 -> 101 & 111 -> 101 -> TRUE
~011 & 111 -> 100 & 111 -> 100 -> TRUE
~100 & 111 -> 011 & 111 -> 011 -> TRUE
~101 & 111 -> 010 & 111 -> 010 -> TRUE
~110 & 111 -> 001 & 111 -> 001 -> TRUE
~111 & 111 -> 000 & 111 -> 000 -> FALSE

--- End code ---

Looks like our general rule seems to hold up. Version A makes less sense than version B, because usually when we do a check against flags, we're concerned about the ones that are set rather than the ones that are clear.

"But what if you wanted to check if all bits of the flag where set, or clear?"

--- Code: ---//Checks if all bits set

// Checks if all bits clear
(Viewer_mode & VM_PADLOCK_ANY) == 0;

--- End code ---

z64555:
As MageKing17 on IRC pointed out, it's preferred to do

--- Code: ---!(Viewer_mode & VM_PADLOCK_ANY)

--- End code ---

when checking if a single bit is clear. This notation also seems to check if all bits are clear.

--- Quote from: z64555 on August 17, 2016, 04:40:00 pm ---when checking if a single bit is clear.

--- End quote ---
what

I didn't say that

z64555:

--- Quote ---4:34:08 PM - z64555: checking if a bit is clear. probably will go with !(Viewer_mode & VM_CAMERA_LOCKED)
4:34:21 PM - MageKing17: yes, that's how you should check that
4:34:36 PM - MageKing17: put a "not" in front of a check if a bit is set
--- End quote ---

Well, that's how I interpreted that. :P