In the previous post, I promised to write a JavaScript program to find formulas that satisfy a given truth table.
Before we can write such a program, we need to understand how to build truth tables in JavaScript.
This post is thus devoted to a discussion of the tools that JavaScript (and similar programming languages) provides to manipulate truth tables and perform Boolean logic operations.
First, it is common to represent the Boolean constants True and False with the integer values 1 and 0, respectively.
Here are two variables declarations that implement this approach in JavaScript:
var True = 1;
var False = 0;
- the & operator for logical conjunction (AND)
- the | operator for logical disjunction (OR)
- the ^ operator for exclusive disjunction (XOR)
Here is a Javascript code sample, in which the function call
console.log( <expression> );
evaluates <expression> and then prints its value in the console window:
console.log( False & False ); // outputs 0, i.e., False
console.log( False & True ); // outputs 0, i.e., False
console.log( True & False ); // outputs 0, i.e., False
console.log( True & True ); // outputs 1, i.e., True
console.log( False | False ); // outputs 0, i.e., False
console.log( False | True ); // outputs 1, i.e., True
console.log( True | False ); // outputs 1, i.e., True
console.log( True | True ); // outputs 1, i.e., True
console.log( False ^ False ); // outputs 0, i.e., False
console.log( False ^ True ); // outputs 1, i.e., True
console.log( True ^ False ); // outputs 1, i.e., True
console.log( True ^ True ); // outputs 0, i.e., False
The case of negation is a bit tricky and will be discussed later.
Of course, in the code sample above, we did not need to declare the variables True and False; we could have used the numbers 0 and 1 directly, like this:
console.log( 0 & 0 ); // outputs 0
console.log( 0 & 1 ); // outputs 0
console.log( 1 & 0 ); // outputs 0
console.log( 1 & 1 ); // outputs 1
console.log( 0 | 0 ); // outputs 0
console.log( 0 | 1 ); // outputs 1
console.log( 1 | 0 ); // outputs 1
console.log( 1 | 1 ); // outputs 1
console.log( 0 ^ 0 ); // outputs 0
console.log( 0 ^ 1 ); // outputs 1
console.log( 1 ^ 0 ); // outputs 1
console.log( 1 ^ 1 ); // outputs 0
As it turns out, the Boolean logic operators in JavaScript are more powerful than we have shown so far.
This is because each integer value they operate on is first converted to a 32-bit string that is the binary encoding of the integer value (read about the binary number system).
So the number 0 is converted to a string of thirty-two 0s, while the number 1 is converted to a string of thirty-one 0s followed by one 1.
In JavaScript, the function call:
parseInt( <binary string> , 2 );
returns the integer value of the given binary string. In other words, it converts a binary string to its equivalent (decimal) integer value.
Here are some examples:
// the next line outputs: 0
console.log( parseInt("00000000000000000000000000000000", 2) );
// the next line outputs: 1
console.log( parseInt("00000000000000000000000000000001", 2) );
// the next line outputs: 2
console.log( parseInt("00000000000000000000000000000010", 2) );
// the next line outputs: 4
console.log( parseInt("00000000000000000000000000000100", 2) );
// the next line outputs: 8
console.log( parseInt("00000000000000000000000000001000", 2) );
// the next line outputs: 9
console.log( parseInt("00000000000000000000000000001001", 2) );
// the next line outputs: 65535
console.log( parseInt("00000000000000001111111111111111", 2) );
Now, the reason I said that Boolean logic operators in JavaScript are more powerful than we have shown so far is because they are really bitwise operators.
This means that, given two integers m and n (i.e., internally, two strings of 32 bits each), they perform their operation in parallel on each one of the 32 pairs of matching bits in m and n.
For example, the operation 8 | 1 is performed on the corresponding bits strings, namely:
00000000000000000000000000001000
| 00000000000000000000000000000001
----------------------------------
00000000000000000000000000001001
00000000000000000000000000001000
& 00000000000000000000000000001001
----------------------------------
00000000000000000000000000001000
Indeed, if you execute the following JavaScript code, you will get the promised output:
console.log( 8 | 1 ) // outputs 9
console.log( 8 & 9 ) // outputs 8
Remember that we built the following truth table for this puzzle:
\(A\) | \(Ac\) | \(B\) | \(Bc\) | \(\varphi = (A \leftrightarrow \varphi_A)\ \&\ (B \leftrightarrow \varphi_B)\) | |
---|---|---|---|---|---|
\(F\) | \(F\) | \(F\) | \(F\) | \(F\) | |
\(F\) | \(F\) | \(F\) | \(T\) | \(F\) | |
\(F\) | \(F\) | \(T\) | \(F\) | \(T\) | only B |
\(F\) | \(F\) | \(T\) | \(T\) | \(F\) | |
\(F\) | \(T\) | \(F\) | \(F\) | \(F\) | |
\(F\) | \(T\) | \(F\) | \(T\) | \(F\) | |
\(F\) | \(T\) | \(T\) | \(F\) | \(T\) | only B |
\(F\) | \(T\) | \(T\) | \(T\) | \(F\) | |
\(T\) | \(F\) | \(F\) | \(F\) | \(T\) | only A |
\(T\) | \(F\) | \(F\) | \(T\) | \(T\) | only A |
\(T\) | \(F\) | \(T\) | \(F\) | \(T\) | both A and B |
\(T\) | \(F\) | \(T\) | \(T\) | \(T\) | only A |
\(T\) | \(T\) | \(F\) | \(F\) | \(F\) | |
\(T\) | \(T\) | \(F\) | \(T\) | \(F\) | |
\(T\) | \(T\) | \(T\) | \(F\) | \(T\) | only B |
\(T\) | \(T\) | \(T\) | \(T\) | \(F\) |
var A = parseInt("0000000011111111",2); // A = 255
var Ac = parseInt("0000111100001111",2); // Ac = 3855
var B = parseInt("0011001100110011",2); // B = 13107
var Bc = parseInt("0101010101010101",2),; // Bc = 21845
Observe that the values listed from top to bottom in each column of the truth table are listed from left to right in the corresponding bit string. This is an arbitrary convention but it must be applied consistently so that the bits (that is, the rows) match up correctly when performing Boolean operations.
For example, we can compute the logical AND of the first two columns in a single operation by computing the value of A & Ac.
In fact, we will define new functions with explicit names, namely, AND, OR, and XOR, so you do not have to overload your brain with recalling the meaning of the &, |, and ^ operators:
function AND(x,y) { return x & y; }; function OR(x,y) { return x | y; }; function XOR(x,y) { return x ^ y; };
console.log( AND(A,Ac) );
More specifically, we will create a variable called onlyA, which corresponds to a column that contains a 1 (True) in each green row and a 0 (False) in all other rows.
Similarly, we define the variables onlyB, both, and none, corresponding to the red, blue, and white rows, respectively:
var onlyA = parseInt("0000000011010000",2);
var onlyB = parseInt("0010001000000010",2);
var both = parseInt("0000000000100000",2);
var none = parseInt("1101110100001101",2);
- logical negation and
- the biconditional connective.
In JavaScript, the ~ operator performs the bitwise logical negation (that is, replaces each 0 with 1, and vice versa) of its integer argument.
Here, we need to remember that each argument to a Boolean operator is converted to a thirty-two bit string.
For example, the value 1 becomes:
Therefore, ~1 is equal to:
which is NOT equal to 0.
[In fact, this binary string (like all binary strings that start with a 1) represents a negative number. You can read more about the so-called two's complement representation; but that topic is not relevant for our discussion of the puzzle.]
What we need to keep in mind is that all of the values to which we apply Boolean operations in JavaScript contain 32 bits.
In contrast, our truth table contains only 16 rows. So, when we wrote:
we only gave the 16 least significant (or rightmost) bits of A. We implicitly used the fact that the 16 most significant (or leftmost) bits of A were 0s.
So, every time we (logically) negate a column of our truth table, we only care about the rightmost 16 bits of the result.
For example, A defined above, internally, is really the 32-bit string:
So, ~A is the 32-bit string:
But we really only care about the 16 least significant (rightmost) bits of it.
So the NOT function is defined as follows:
which masks (that is, sets to 0) the 16 leftmost bits by performing a logical AND with the constant value that starts with sixteens 0s and ends with sixteen 1s.
Finally, the biconditional (or if-and-only-if, often abbreviated IFF) connective can be implemented as follows:
since XOR returns 1 when its two arguments are different and 0 otherwise, which is the logical negation of the behavior of the biconditional (check their truth tables if needed).
Now that we know how to compute truth tables in JavaScript, we can turn to the design of the algorithm for our program to solve the puzzle.
This will be the focus of the next post.
Here, we need to remember that each argument to a Boolean operator is converted to a thirty-two bit string.
For example, the value 1 becomes:
00000000000000000000000000000001
Therefore, ~1 is equal to:
11111111111111111111111111111110
[In fact, this binary string (like all binary strings that start with a 1) represents a negative number. You can read more about the so-called two's complement representation; but that topic is not relevant for our discussion of the puzzle.]
What we need to keep in mind is that all of the values to which we apply Boolean operations in JavaScript contain 32 bits.
In contrast, our truth table contains only 16 rows. So, when we wrote:
var A = parseInt("0000000011111111",2); // A = 255
So, every time we (logically) negate a column of our truth table, we only care about the rightmost 16 bits of the result.
For example, A defined above, internally, is really the 32-bit string:
00000000000000000000000011111111
So, ~A is the 32-bit string:
11111111111111111111111100000000
But we really only care about the 16 least significant (rightmost) bits of it.
So the NOT function is defined as follows:
function NOT(x) { return ~x & 65535; };
Finally, the biconditional (or if-and-only-if, often abbreviated IFF) connective can be implemented as follows:
function IFF(x,y) { return NOT( XOR(x,y) ); };
To recap, let me list here ALL of the JavaScript code that we discussed in this post and that we will use in our final program:
var A = parseInt("0000000011111111",2);
var Ac = parseInt("0000111100001111",2);
var B = parseInt("0011001100110011",2);
var Bc = parseInt("0101010101010101",2);
var onlyA = parseInt("0000000011010000",2);
var onlyB = parseInt("0010001000000010",2);
var both = parseInt("0000000000100000",2);
var none = parseInt("1101110100001101",2);
function AND(x,y) { return x & y; };
function OR(x,y) { return x | y; };
function XOR(x,y) { return x ^ y; };
function NOT(x) { return ~x & 65535; };
function IFF(x,y) { return NOT( XOR(x,y) ); };
Now that we know how to compute truth tables in JavaScript, we can turn to the design of the algorithm for our program to solve the puzzle.
This will be the focus of the next post.
No comments:
Post a Comment