In Arithmetic and Expressions we looked at various types of numeric data, including whole numbers and floating-point numbers. We showed all of the arithmetic operations that we could apply to those various kinds of numbers. In Truth, we’ll introduce another type of data to represent truth. In Logic we’ll manipulate those truth values with logic operators.
Mathematically, this is the essential foundation of all computing. Pragmatically, however, this is not as interesting, so we’ve pushed it back to here.
The domain of arithmetic involves a large number of values: there are billions of integer values and an almost unlimited range of long integer values. There are also a wide variety of operations, including addition, subtraction, multiplication, division, remainder and others, too numerous to mention. The domain of logic involves two values: False and True, and a few operations like and, or and not.
This world of logic bridges language, philosophy and mathematics. Because we deal with logic informally all the time, it can seem needless to define a formal algebra of logic. Computers, however, are formally defined by the laws of logic, so we can’t really escape these definitions. If you don’t have any experience with formal logic, there’s no call for panic: with only two values (true and false), how complex can the subject be? Mostly, we have to be careful to follow these formal definitions, and set aside the murky English-language idioms that masquerade as logic.
We also have to be careful to avoid confusing logic and rhetoric. A good public speaker often uses rhetorical techniques to make their point. In some cases, the rhetoric will involve logic, but other times, it will specifically avoid logic. One example is to attack the speaker personally, rather than attack the logic behind the point they’re trying to make. Political debates include many examples of rhetorical techniques that have a certain kind of logic, but aren’t grounded in the kind of formal mathematical logic that we’re going to present here.
Truth. Python has a number of representations for truth and falsity. While we’re mostly interested in the basic Python literal of False and True, there are several alternatives.
False.
Also 0, the complex number 0+0j, the special value None, zero-length strings "", zero-length lists [], zero-length tuples (), and empty mappings {} are all treated as False. We’ll return to these list, tuple and map data structures in later chapters. For now, we only need to know that a structure that is empty of meaningful content is effectively False.
True.
Anything else that is not equivalent to False. This means that any non-zero number, or any string with a length of one or more characters are equivalent to True.
What about “maybe’s” and “unknown’s”? You’ll need a good book on more advanced logic systems if you want to write programs that cope with shades of meaning other than simple true and false. This kind of fuzzy logic isn’t built in to Python. You could write your own extension module to do this.
The bool Function. Python provides a factory function to provide the truth value of any of these objects. In effect, this collapses any of the various forms of truth down to one of the two explicit objects: True or False.
We can see how this works with the following examples.
>>> bool(1)
True
>>> bool(0)
False
>>> bool( "a string" )
True
Python provides three basic logic operators that work on the domain of True and False values: not, and and or. This domain and the applicable operators forms a complete algebraic system, sometimes called a Boolean algebra, after the mathematician George Boole.
In Python parlance, the data values of True and False, plus the operators not, and and or define a data type. In Simple Arithmetic : Numbers and Operators we saw a number of numeric data types, and we’ll look at yet more data types as we learn more about Python.
Truth Tables. The boolean data type has only two values, which means that we can define the boolean operators by enumerating all of the possible results in a table. Each row of the table has a unique combination of True and False values, plus the result of applying the logic operator to those values. There are only four combinations, so this is a pretty tidy way to define the operators.
We wouldn’t want to try this for integer multiplication, since we have almost four billion integer values (including both negative and positive values), which would lead to a table that enumerates all 18 quintillion combinations.
Here’s an example of a truth table for some hypothetical operator we’ll call cake. Rather than show and, or or not specifically, we’ll use a made-up operator so we can show how a truth table is built.
This table shows all possible results for x cake y. It shows all four combinations of inputs and the result of applying our logic operation to those values.
| x | y | x cake y |
|---|---|---|
| True | True | True cake True = False |
| True | False | True cake False = True |
| False | True | False cake True = True |
| False | False | False cake False = False |
The not Operator. The following little program creates a truth table that shows the value of not x for both vales of x. It may seem silly to take such care over the obvious definition that not True is False. However, we can use this technique to help us visualize more complex logical operations.
print "x", "not x"
print True, not True
print False, not False
| x | not x |
|---|---|
| True | False |
| False | True |
The and Operator. This next little program creates a truth table that shows the value of x and y for all four combination of True and False. You can see from this table that x and y is only True if both of the terms are True. This corresponds precisely to the English meaning of “and”.
print "x", "y", "x and y"
print True, True, True and True
print True, False, True and False
print False, True, False and True
print False, False, False and False
| x | y | x and y |
|---|---|---|
| True | True | True |
| True | False | False |
| False | True | False |
| False | False | False |
The or Operator. The following table shows the evaluation of x or y for all four combination of True and False. You can see from this table that x or y is True if one or both of the terms are True. In English, we often emphasize the inclusiveness of this by writing “and/or” . We do this to distinguish it from the English-language “exclusive or”, (sometimes written “either/or”) which means “one or the other but not both”. Python’s x or y is the inclusive sense of “or”.
| x | y | x or y |
|---|---|---|
| True | True | True |
| True | False | True |
| False | True | True |
| False | False | False |
An important note is that and is a higher priority operator than or, analogous to the way multiplication is higher priority than addition. This means that when Python evaluates expressions like a or b and c, the and operation is evaluated first, followed by the or operation. This is equivalent to a or (b and c).
Tip
Debugging Logic Operators
The most common problem people have with the logic operators is to mistake the priority rules. The lowest priority operator is or; and is higher priority and not is the highest priority. If there is any confusion, extra parentheses will help.
Other Operators. There are – theoretically – more logic operators. However, we can define all of other the other logic operations using just not, and and or. Other logic operations include things like “if-then”, “if-and-only-if”. For example, “if a then b” can be understand as (a and b or not a).
One of the more important additional logic operations is “or in an exclusive sense”, sometimes called one-or-the-other-but-not-both or exclusive or, abbreviated xor. We can understand “a xor b” as ((a or b) and not (a and b)). The parenthesis are required to create the correct answer.
How can we prove this? Write a short program like the following:
a, b = True, True
print a, b, ((a or b) and not (a and b))
a, b = True, False
print a, b, ((a or b) and not (a and b))
You’ll have to repeat this for False, True and False, False combinations, also.
The claim that we can define all logic operations using only not, and and or is a fairly subtle piece of mathematics. We’ll just lift up a single observation as a hint to how this is possibly true. We note that given two values and an operation, there are only four combinations of values in the truth table. There are only 16 possible distinct tables built from four boolean values. The logic puzzle of creating each of the 16 results using only not, and and or isn’t terribly hard. For real fun, you can try constructing all 16 results using only the not-and operator, sometimes called “nand”.
[I love that title.]
Logic Short-Cuts.
We have several versions of false: False, 0, None, '', (), [] and {}. We’ll cover all of the more advanced versions of false in Basic Sequential Collections of Data. For each of the following, work out the value according to the truth tables and the evaluation rules. Since each of the boolean values is unique, we can see which part of the expression was evaluated.
Exclusive Or.
Python’s or operator is the inclusive or, sometimes written “and/or” in English. The exclusive or has a more formal meaning of x or y but not both. This phrase “but not both” can be implemented as a logic test.
Remember, first, that “but” means “and”. This gives us a hint on how we can proceed. We’ll need to write an expression that starts (x or y) and .... What does “both” mean in this context? Would x and y implement what we mean by both?
Does (x or y) and not (x and y) create the correct truth table for “exclusive or”?
Not And.
Engineers, in an effort to save money creating digital logic, have determined that the not-and operation can be used for a variety of purposes. The engineers call it “nand”. We don’t really have a proper English phrase for nand, but we can write the nand in Python as not (a and b).
Create a truth table for the basic nand operation (not (a and b)), showing all four results.
Create a truth table for not (a and a). What truth table does this match?
What happens when we combine these operations? If we wanted to do the equivalent of something really complex like “(a nand b) nand (a nand b)”, we have to do some algebra, resulting in something like not ((not (a and b)) and (not (a and b))). What truth table does this match?
If and Only If.
In English, we’ll sometimes use the phrase “if and only if”, which we might want to abbreviate iff. When we look at the formal meaning of a hypothetical x iff y, we need it to be true when x and y have the same truth value. This means that x and y are both true or x and y are both false.
Does (x and y) or (not x and not y) create the correct truth table for “if and only if”?
We haven’t covered the == operator, but you should also try x == y to see if this also works.
Implies.
The word implies has a formal logic definition. We say “a implies b” as a short form of “if a, then b”. We might say “rain implies a wet lawn”, or “if it rains, then the lawn gets wet”. In Python, we might write a implies b if Python had a logic operator named implies. When we look at the formal meaning of our hypothetical x implies y, we want it to be true when x and y are true. When x is false, the truth or falsity of y doesn’t really matter. We can say that implication is true when both x and y are true or x is false.
Does (x and y) or (not x) create the correct truth table for “implies”?
There are two schools of thought on this subject:
Boolean data is a unique type of data: it has a unique domain of values and unique operators. The domain is really tiny (True and False), but it is a proper mathematical domain with as many interesting properties as whole numbers, rational numbers or irrational numbers. For this reason, it deserves its own data type.
Claiming that the values of True and False are really just aliases for 1 and 0 misses two important points. First, from a historical perspective, the computer engineers borrowed Boole’s algebra of logic and used it to build computer circuits. The proper historical context shows us that the engineers figured out how to use the ideas of True and False to build electronic circuits that could be interpreted as meaning 1 and 0.
Second, and more important, the standard approach to avoiding a boolean type is to use the special operators (Operators for Bit Manipulation. This doesn’t eliminate the boolean type, it just eliminates plain boolean literals. We have a domain of values (1 and 0) and a suite of operators (&, |, ^, and ~) on that domain of values. This creates an ambiguity over the meaning of 1: does it mean the number one or True?
This logic stuff seems to be “Over The Top” for something that is common sense.
When talking to another intelligent human being, this many appear as needless fussiness over something that is common sense. However, we’re not talking with people, we’re writing a program in the formal language of Python which will control a mindless collection of transistors. We need to be as precise and inflexible as the circuitry in our computer.
The “almost unlimited” at the beginning of the chapter was unsatisfyingly vague.
The domain of values for floating-point numbers is technically
finite. The domain depends, to a small extent, on your computer.
We’ll assume 64-bit floating point numbers. These have
distinct values, which is 18.4 quintillion.
These values, however, are spread over a
range that includes
(approximately
)
as the number closest to zero,
and
(approximately
)
as the number furthest from zero.
Some computers have 80-bit floating-point numbers. The ranges in this case would obviously be somewhat larger.