Skip to main content

Comparisons and boolean logic

Comparisons such as score >= 50 answer True or False. Chain them as in 0 <= score <= 100, combine them with and, or and not, see how and and or stop as soon as the answer is known, and learn why 0.1 + 0.2 == 0.3 is False and how to compare floats safely.
P01.2.4L0 Foundation25 minIn review

Before this lesson

P01.2.3 Arithmetic and expressions

After this lesson you can

  • Write comparisons with ==, !=, <, <=, > and >=, chain them, and tell = (bind) from == (ask).
  • Combine conditions with and, or and not, using their truth tables and their order.
  • Predict when and and or stop early, and use that to guard an expression that could fail.
  • Explain why 0.1 + 0.2 == 0.3 is False, and compare floats with a tolerance, abs(a - b) < 1e-9.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

The quiz is out of 100 and the pass mark is 50. How do you ask Python whether Ama passed, whether a score is possible at all, and which students passed without a distinction? And why does Python insist that 0.1 + 0.2 == 0.3 is False?

From the last lesson

1. In what order does Python work out 2 + 3 * 4 ** 2, and what is the value?

Show answer

The power first, 4 ** 2 is 16; then the product, 3 * 16 is 48; then the sum, 2 + 48 is 50.

2. What are 23 // 4 and 23 % 4, and how do they fit a = (a // b) * b + a % b?

Show answer

5 and 3, and \({5 \times 4 + 3 = 23}\).

Intuition

A comparison asks a yes-or-no question about values, such as “is this score at least 50?”. Its answer is a bool: True or False. On a number line, a comparison splits the numbers in two: the ones for which the answer is True and the ones for which it is False.

A number line from 30 to 90 with the five study-table scores as blue dots, each labelled with a name and score above: Dina 40, Ama 55, Eli 60, Ben 65 and Chen 80. An orange dashed line marks the pass mark 50, and a thick vermillion band runs along the line from 50 upwards. Under each dot is the answer to score >= 50 in vermillion: False for Dina, True for Ama, Eli, Ben and Chen. A note reads True from 50 up, False below.
P01.2.4-F1

The question score >= 50 asked of the five scores. Everything from the pass mark up answers True, everything below answers False.

A cut-off on a number is the simplest kind of classifier: “predict a pass when the score is at least 50”. Machine learning uses the same shape with a learned score and a chosen threshold, and lesson ML01.1.1 begins with a threshold rule of this kind.

Build it up

Step 1. Six comparison operators.

Operator Asks Example Value
== equal? 55 == 55 True
!= not equal? 55 != 55 False
< less than? 40 < 50 True
<= at most? 50 <= 50 True
> more than? 40 > 50 False
>= at least? 55 >= 50 True

Step 2. = binds, == asks. score = 50 is an instruction: bind the name to 50. score == 50 is a question with an answer, True or False. Mixing them up is one of the commonest slips in programming.

Step 3. Chains. 0 <= score <= 100 reads as it does in maths and means 0 <= score and score <= 100. A pass that is not a distinction, with the cut-offs 50 and 70, is 50 <= score < 70.

Step 4. and, or and not. a and b is True only when both are True. a or b is True when at least one is True, both included. not a turns True into False and False into True. Written out row by row, in the order used by lesson M01.6.2:

a b a and b a or b
True True True True
True False False True
False True False True
False False False False
Four number lines from 30 to 90, one under another, each with the five scores as small blue dots and a thick vermillion stretch where the expression written above it is True. score >= 50: from a filled dot at 50 to the right end, True for 4 of 5. score < 70: from the left end to a hollow dot at 70, True for 4 of 5. score >= 50 and score < 70: from a filled dot at 50 to a hollow dot at 70, True for 3 of 5. score < 50 or score >= 70: from the left end to a hollow dot at 50 and from a filled dot at 70 to the right end, True for 2 of 5. A note: a filled end is included, a hollow end is left out.
P01.2.4-F2

and keeps only the stretch where both tests hold; or keeps every stretch where at least one holds. The third line is the pass band, the fourth its opposite.

Step 5. The order. Arithmetic comes first, then comparisons, then not, then and, then or. So score >= 50 and score < 70 needs no brackets, not score >= 50 means not (score >= 50), and a or b and c means a or (b and c). When in doubt, add brackets: they never change a correct answer.

Step 6. Stopping early. and and or work out their left side first and stop as soon as the answer is known. If the left side of an and is False, the whole is False whatever the right side says, so the right side is never worked out. If the left side of an or is True, the whole is True, and again the right side is skipped. The worked example uses this to avoid a division by zero.

The formal statement

NoteDefinitions: comparisons and the boolean operators

A comparison a == b, a != b, a < b, a <= b, a > b or a >= b gives a bool, True or False. A chain a < b < c means a < b and b < c, except that b is worked out only once. For bools a and b:

  • a and b works out a. If a is False the answer is False, and b is not worked out at all. Otherwise the answer is b.
  • a or b works out a. If a is True the answer is True, and b is not worked out. Otherwise the answer is b.
  • not a is True when a is False, and False when a is True.

The two rules for and and or give exactly the truth table of Step 4, and they say more: which side is worked out. This lesson only gives and and or the values True and False. Given other values, they hand back one of the two values themselves; lesson P01.3.1 explains which values count as true.

The order, continued. The table of lesson P01.2.3 ended with + and -. After them come:

Order Operators
6 == != < <= > >=
7 not
8 and
9 or

Comparing floats. a == b is True only when a and b are the very same float. Lesson P01.2.1 showed that two calculations that agree on paper can end one float apart, and then == says False. So floats are compared with a tolerance:

\[ \abs{a - b} < 10^{-9} \]

which asks whether the two are within 0.000000001 of each other. In Python this is abs(a - b) < 1e-9, where 1e-9 is Python’s short way to write \({1 \times 10^{-9}}\): the number after the e says how many places to move the point, here 9 to the left. The tolerance is a choice. One float operation on numbers near 1 is off by at most about 0.0000000000000001, so the errors of a few operations are still millions of times smaller than 1e-9, while a real difference between two quiz results is far larger. Lesson P01.5.1 gives the library version, math.isclose.

Worked example

Part 1. For each student, is the score a pass (score >= 50), and is it a pass without a distinction (50 <= score < 70)?

Student Score score >= 50 50 <= score < 70
Ama 55 True True
Ben 65 True True
Chen 80 True False
Dina 40 False False
Eli 60 True True

Four of the five pass at the mark of 50. Three pass without a distinction. Chen’s 80 passes the first test but fails score < 70, so he has the one distinction, and Dina’s 40 is the one fail.

Part 2. A study rate, points per hour, is score / hours. Ask whether a student scored at least 20 points per hour, safely, even for a new student who studied for 0 hours.

Dividing by 0 stops a program with a ZeroDivisionError (lesson P01.2.3). The guard is to ask hours > 0 first, joined by and:

Two rows evaluating hours > 0 and score / hours >= 20. Row one, Eli with hours 3 and score 60: blue box hours > 0, the word and, blue box score / hours >= 20, and a green arrow to a vermillion box True; under the boxes, step 1, 3 > 0 is True, and step 2, 60 / 3 >= 20 is True. Row two, a new student with hours 0 and score 35: blue box hours > 0, the word and, a grey dashed box score / hours >= 20, and a green arrow that jumps over the grey box to a vermillion box False; under the boxes, step 1, 0 > 0 is False, and step 2, never worked out. A note: no division by 0, so no ZeroDivisionError.
P01.2.4-F3

The same expression for two students. When the left side of and is False, Python jumps straight to the answer, and the division is never attempted.

Eli, 3 hours, score 60. 3 > 0 is True, so and must look at its right side: \({60 / 3 = 20.0}\), and 20.0 >= 20 is True. The answer is True.

A new student, 0 hours, score 35. 0 > 0 is False, so the answer is False, and Python stops there. 35 / 0 is never worked out, so there is no error. Written the other way round, as score / hours >= 20 and hours > 0, the division would come first and the program would stop.

Code

Comparisons and chains on Ama’s score.

score = 55
print(score >= 50, score == 50, score != 50)
print(0 <= score <= 100, 50 <= score < 70)
print(not score >= 50)
True False True
True True
False

Counting the passes. A bool behaves like a number in arithmetic, True as 1 and False as 0, so adding the five answers counts the Trues:

ama = 55
ben = 65
chen = 80
dina = 40
eli = 60
passes = (ama >= 50) + (ben >= 50) + (chen >= 50) + (dina >= 50) + (eli >= 50)
band = (50 <= ama < 70) + (50 <= ben < 70) + (50 <= chen < 70) + (50 <= dina < 70) + (50 <= eli < 70)
print(passes, band)
4 3

Counting Trues this way is the first half of a classifier’s accuracy, the share of its answers that are correct (lesson ML01.7.1): add up a True for every correct answer, then divide by the number of answers. Next, the guard from the worked example:

hours = 3
score = 60
print(hours > 0 and score / hours >= 20)
hours = 0
score = 35
print(hours > 0 and score / hours >= 20)
True
False

Last, the float trap and its cure:

print(0.1 + 0.2 == 0.3)
print(abs((0.1 + 0.2) - 0.3))
print(abs((0.1 + 0.2) - 0.3) < 1e-9)
False
5.551115123125783e-17
True

The middle line prints 5.551115123125783e-17, which is \({5.55\ldots \times 10^{-17}}\): the two floats differ by one step, far less than the tolerance. == is right that they differ; the tolerance is right that the difference does not matter.

Explore

This module’s interactive, the memory-diagram builder, lives in lesson P01.2.2. Its read-out tells you which names share an object, the question that is asks, which is a different question from ==.

Pitfalls

Two panels. Left, marked with a cross and titled Exact ==: the code 0.1 + 0.2 == 0.3 above a number line with a blue dot labelled 0.3 and a vermillion dot labelled 0.1 + 0.2 just to its right, noted one step apart; underneath, same float? False. Right, marked with a tick and titled A tolerance: the code abs((0.1 + 0.2) - 0.3) < 1e-9 above the same two dots, both inside a green band labelled the band, 1e-9 either side; underneath, close enough? True, with the note not to scale.
P01.2.4-F4

Wrong beside right. == asks for the very same float, and the two results are one step apart. A tolerance asks whether they are close enough, and they are.

“0.1 + 0.2 == 0.3 should be True, so Python has a bug.”

The two sides are neighbouring floats, one tiny step apart, and == compares floats exactly. Compare floats with a tolerance, abs(a - b) < 1e-9. This is why later courses never test a loss or a metric for exact equality.

Two panels. Left, marked with a cross and titled Joined with or: the code score >= 0 or score <= 100 above two number lines, the first vermillion from 0 to the right edge and the second vermillion from the left edge to 100; a blue dot at 150 sits on the first stretch. Note: every number passes one of the two; score = 150, True. Right, marked with a tick and titled A chain: the code 0 <= score <= 100 above a number line that is vermillion only from 0 to 100, ends filled; the blue dot at 150 lies outside. Note: only 0 to 100 passes, ends included; score = 150, False.
P01.2.4-F5

Wrong beside right. Joined with or, the two tests cover the whole number line, so the impossible score 150 gets through. The chain keeps only 0 to 100.

“score >= 0 or score <= 100 checks that a score is possible.”

Every number is at least 0 or at most 100, so this is True even for 150 or −5. A range check needs both tests at once: score >= 0 and score <= 100, or the chain 0 <= score <= 100.

“score > 50 or < 70 is a short way to write it.”

Each side of or must be a whole comparison. Python cannot read the line, and stops with SyntaxError: invalid syntax before running anything (Python 3.15). Write score > 50 or score < 70, and check that or is really what you mean.

“= and == do the same job.”

score = 50 binds the name and answers nothing; score == 50 asks a question and changes nothing. In a line that should test, a single = is a mistake.

Check yourself

Recall

1. What is the difference between = and ==?

Show answer

= binds a name to a value; == asks whether two values are equal and gives True or False.

2. What does 0 <= score <= 100 mean, written with and?

Show answer

0 <= score and score <= 100, with score worked out only once.

3. What are True and False, True or False and not True or True?

Show answer

False, True and True. In the last one not comes first: not True is False, and False or True is True.

4. In hours > 0 and score / hours >= 20, which part is skipped when hours is 0, and why does that matter?

Show answer

The right side, score / hours >= 20. The left side is False, so and already knows the answer is False. Skipping the right side means that the division by 0, which would stop the program, is never attempted.

5. Why is 0.1 + 0.2 == 0.3 False, and what should you write instead?

Show answer

The sum and 0.3 are stored as two neighbouring floats, and == compares floats exactly. Write abs((0.1 + 0.2) - 0.3) < 1e-9, which is True.

Exercises

1. ★ With score = 65, work out score > 60, score == 65.0, score != 65, 60 <= score < 70 and not score < 50.

Show solution

True, because 65 is more than 60. True, because == compares the values, and the int 65 and the float 65.0 have the same value. False, because 65 equals 65. True, because both 60 <= 65 and 65 < 70 hold. True, because score < 50 is False and not turns it into True.

2. ★★ With the cut-offs 70 and 50, write one expression for each band: a distinction, a pass without a distinction, and a fail. Check them on the five scores and count each band.

Show solution

Distinction: score >= 70. Pass: 50 <= score < 70. Fail: score < 50. Exactly one of the three is True for any score, because together they cover the number line once.

On the scores: Chen’s 80 is the only distinction. Ama’s 55, Ben’s 65 and Eli’s 60 are passes. Dina’s 40 is the only fail. That is one distinction, three passes and one fail.

3. ★★★ Fill in not (a and b) and (not a) or (not b) for the four rows of the truth table and show that they always agree. Then use this to rewrite not (score >= 50 and score < 70) without not.

Show solution

Write L for not (a and b) and R for (not a) or (not b), and T and F for True and False, as lesson M01.6.2 does:

a b L R
T T F F
T F T T
F T T T
F F T T

Row 1: a and b is True, so its not is False; on the right, both not a and not b are False, so their or is False. In the other three rows at least one of a, b is False, so a and b is False and its not is True; on the right, at least one not is True, so the or is True. The columns agree in every row. This is one of De Morgan’s laws, which lesson M01.6.1 draws for sets and lesson M01.6.2 proves for statements.

With a as score >= 50 and b as score < 70, the law gives (not score >= 50) or (not score < 70), which is score < 50 or score >= 70: a fail or a distinction, the fourth line of the figure in Step 4.

Summary card

A comparison answers True or False; and, or and not combine the answers, and and and or stop as soon as the answer is known.

The anchor figure again: the five scores on a number line, with score >= 50 True for Ama, Eli, Ben and Chen and False for Dina.
P01.2.4-F1

One question, five answers.

  • == != < <= > >= give a bool; = binds, == asks; 0 <= score <= 100 is a chain.
  • and is True when both are, or when at least one is; not flips. Order: not, and, or.
  • a and b skips b when a is False; a or b skips b when a is True.
  • Never compare floats with ==: write abs(a - b) < 1e-9.

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, Built-in Types, sections Boolean Operations and Comparisons: or and and are short-circuit operators that evaluate their second argument only when needed; not a == b is read as not (a == b); the table of the comparison operators; and comparisons can be chained.
  • Python Software Foundation, Expressions, The Python Language Reference. Section 6.10: x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once. Section 6.11: x and y first evaluates x and returns it if it is false, otherwise evaluates y, and x or y the other way round. Section 6.17: comparisons, then not, then and, then or.
  • Python Software Foundation, The standard type hierarchy, The Python Language Reference, Section 3.2: the Boolean type is a subtype of the integers, and False and True behave like 0 and 1.
  • Python Software Foundation, Floating-point literals, The Python Language Reference: 1.166e-5 represents \({1.166 \times 10^{-5}}\).
  • Python Software Foundation, Floating-Point Arithmetic: Issues and Limitations, The Python Tutorial, Chapter 15: 0.1 + 0.1 + 0.1 == 0.3 is False, and math.isclose is offered for comparing inexact values.
  • Allen B. Downey, Think Python, 3rd edition, Chapter 5, Sections 5.2 and 5.3: == against =, the relational operators, and and, or and not.

Next. Module P01.3 Control flow puts these conditions to work: its first lesson, P01.3.1, uses them to choose which lines of a program run. To see the same logic as truth tables and Venn diagrams, lesson M01.6.2 of Math Foundations is a good companion.

This lesson unlocks

P01.3 Control flow

Your progress is saved in this browser only. No account, no tracking.
Back to top