Operators and Type Conversion - Making Values Interact
Context
Operators are the symbols that make values interact. Arithmetic ones combine numbers, comparison ones ask a question about two values and answer true or false, and logical ones combine those answers into a single verdict.
This is the arithmetic of every metric you will ever compute. Accuracy is a division, an error is a subtraction, and deciding whether a run beat the previous one is a comparison. Deciding whether a row is usable is usually several comparisons joined together, a price above zero and a floor area recorded and a region name present, all true at once before the row is kept.
Two things break here, one silently and one loudly. Mixed expressions group by a fixed precedence that is not left to right, so a mean written without the right grouping returns a number that is plausible and wrong. Converting between types can fail outright on text that does not hold a number, which is exactly what a malformed row contains. The previous lesson gave values names, this lesson makes those named values produce new ones.
Goal
- Combine numeric values with all seven arithmetic operators and predict each result's type.
- Produce booleans by comparing values with
==,!=,<,>,<=, and>=. - Combine truth-values with
and,or, andnotto express compound conditions. - Predict the result of a mixed expression by applying operator precedence in the correct order.
- Convert a value deliberately between
int,float, andstr, and explain when a conversion fails.
Prerequisites
Neither of these is required, and the lesson introduces every operator from scratch regardless. They are here only if you would rather see the whole family at once before meeting them one at a time.
- YouTube - "Learn Python - Full Course for Beginners [Tutorial]" by freeCodeCamp.org. The chapters "Working With Numbers", "If Statements" and "If Statements & Comparisons". Watch for one picture, an operator as a small machine that takes values in and gives one value out.
- Official docs - Python Language Reference, "Operator precedence". The authoritative precedence table, worth knowing exists before you need it.
Table of Contents
- Arithmetic Operators
- Comparison Operators
- Logical Operators
- Operator Precedence
- Type Conversion
Theory
Arithmetic Operators
What. The arithmetic operators are the seven symbols that do math on numbers: + (add), - (subtract), * (multiply), / (true divide), // (floor divide), % (modulo/remainder), and ** (power).
Why. Every numeric computation a program performs, averaging losses, counting batches, scaling features, is built from these seven. They are the verbs of arithmetic.
When. Use them whenever you compute a new number from existing numbers, which in data work is constantly.
Nothing needs installing, these operators are part of the core language, available the instant the interpreter starts. The four obvious ones (+, -, *, **) behave as expected. The interesting cases are the three division-related operators, which trip up nearly every beginner.
# '+' adds, '-' subtracts, '*' multiplies, on two integers each gives an integer.
print(7 + 3) # 10
print(7 - 3) # 4
print(7 * 3) # 21
# '**' is exponentiation: 2 to the power 10.
print(2 ** 10) # 1024The one to note is **, which is power. The caret ^ means something else entirely in Python, and on two integers it quietly hands back a number rather than an error. With two integer operands, + - * return integers, and so does ** with an exponent of zero or more (2 ** -1 is 0.5), so counting math stays exact.
Now the three division operators, which are the classic stumble. / is true division and always returns a float, even when the result is whole. // is floor division, which divides and rounds down to a whole number. % is modulo, the remainder left after floor division.
# '/' is TRUE division: always returns a float, even for a clean division.
print(10 / 2) # 5.0 (a float, note the .0)
# '//' is FLOOR division: divides then rounds DOWN to a whole number.
print(10 // 3) # 3 (3.33... floored to 3)
# '%' is MODULO: the remainder after floor division.
print(10 % 3) # 1 (10 = 3*3 + 1, remainder 1)The surprise in the first line is that dividing two integers handed back a decimal point, and it does that every time, whether or not the division comes out clean. The other two work as a pair: // gives how many whole times the divisor fits, % gives what is left over, which is exactly how you would split a dataset of 10 rows into groups of 3 and know what the last short group holds.
DON'T:
- DON'T assume
10 / 2is the integer5,/always returns a float, here5.0. - DON'T use
^for powers,2 ^ 10does not mean "2 to the 10th" in Python, use2 ** 10.
Comparison Operators
What. The comparison operators ask a yes/no question about two values and produce a boolean: == (equal), != (not equal), < (less than), > (greater than), <= (at most), >= (at least).
Why. Every decision a program makes rests on a comparison, is accuracy above the threshold, is this value missing, has the loss stopped improving. Comparisons are the factories that produce the booleans you met in Lesson 1.1.2.
When. Use a comparison whenever you need a true/false answer about how two values relate, which feeds directly into the if statements coming soon.
The single most common beginner error lives here: = assigns, == compares. One equals sign binds a name (Lesson 1.1.3), two equals signs ask "are these equal?" and return True or False.
# '==' asks "are these equal?", it returns a boolean, it does NOT assign.
print(64 == 64) # True
# '!=' asks "are these DIFFERENT?".
print(64 != 32) # True
# '<' and '>' compare order and also return booleans.
print(0.97 > 0.9) # True
print(0.5 < 0.5) # FalseThe result of every comparison is exactly one of the two truth-values, which makes a comparison the natural right-hand side of a yes/no decision. Notice 0.5 < 0.5 is False because < is strictly less than, where 0.5 <= 0.5 would be True.
Comparisons work across the numeric types and on strings too. Comparing an int to a float compares their values (5 == 5.0 is True), and strings compare letter by letter, with uppercase A to Z all sorting before lowercase a to z. But beware the float imprecision from Lesson 1.1.2, exact equality on floats is unreliable.
# Mixed int/float comparison: values are compared, so this is True.
print(5 == 5.0) # True
# Float imprecision strikes: this is False, NOT True, due to rounding.
print(0.1 + 0.2 == 0.3) # FalseThose two lines are the practical reason never to test a float for exact equality. == compares numeric value rather than type, so it happily crosses int and float, and yet it still reports False for two things that mathematically are equal, because one of them is stored as 0.30000000000000004.
DON'T:
- DON'T use
=when you mean to compare,if x = 5is an error, you wantif x == 5. - DON'T test floats with
==,0.1 + 0.2 == 0.3isFalse, compare with a tolerance instead.
Logical Operators
What. The logical operators combine or invert booleans: and is True only when both sides are True, or is True when at least one side is True, and not flips a boolean to its opposite.
Why. Real decisions rarely hinge on a single condition, you keep training only if loss improved and you have epochs left, you stop if accuracy is high or time ran out. Logical operators express these compound conditions.
When. Use them whenever a decision depends on more than one truth-value, or when you need to negate a condition.
These are words, not symbols, and, or, not, which makes compound conditions read almost like English. They take booleans (typically produced by comparisons) and produce a single boolean.
# 'and' is True only if BOTH sides are True.
print(True and True) # True
print(True and False) # False
# 'or' is True if AT LEAST ONE side is True.
print(False or True) # True
print(False or False) # False
# 'not' flips the boolean.
print(not True) # FalseIn practice the sides are comparisons rather than bare literals, which is where these become useful.
# A compound condition built from two comparisons joined by 'and'.
accuracy = 0.97
loss = 0.05
is_good_model = accuracy >= 0.9 and loss < 0.1
print(is_good_model) # True
# 'not' negates a whole condition.
print(not (accuracy < 0.5)) # TrueNotice what the is_good_model line did not need: no parentheses around either comparison. Each comparison groups as a whole before and joins the two, which is precedence doing its job, and the next subtitle is where that order gets stated properly. This is exactly how you will express "accept this model only if it is both accurate and low-loss" once you have if statements.
DON'T:
- DON'T use
&,|, or!for logic, Python uses the wordsand,or,not(on two integers&and|are bitwise operators, which combine them digit by digit in binary, on two sets they are intersection and union, Lesson 1.4.3, and!alone is not an operator at all). - DON'T write
accuracy >= 0.9 and < 0.1, each side ofandmust be a complete expression, writeaccuracy >= 0.9 and loss < 0.1.
Operator Precedence
What. Operator precedence is the fixed ranking that decides how Python groups an expression that mixes several operators, like the order-of-operations rules from arithmetic, extended to comparisons and logic.
Why. Without a fixed order, 2 + 3 * 4 would be ambiguous. Precedence guarantees every expression has exactly one meaning, and knowing it lets you predict results and avoid silent bugs.
When. It matters in every expression with more than one operator, and most of all when you are tempted to assume an order that is wrong.
The ranking, from highest (grouped first) to lowest (grouped last), for what you know so far: ** first, then unary minus -x, then * / // %, then + -, then the comparisons == != < > <= >=, then not, then and, then or. Operators of the same level apply left to right (except **, which goes right to left, and the comparisons, which chain instead of grouping, so 0 < x < 1 means 0 < x and x < 1).
# '*' binds tighter than '+', so 3*4 happens first: 2 + 12 = 14.
print(2 + 3 * 4) # 14, not 20
# '**' binds tighter than '+', so 2**3 happens first: 8 + 1 = 9.
print(2 ** 3 + 1) # 9
# Arithmetic outranks comparison: 2+3=5, then 5 > 4 is True.
print(2 + 3 > 4) # TrueThe third line is the one that generalizes: arithmetic outranks comparison, so each side of a comparison is computed in full before that comparison is made, which is why a comparison can take a whole expression on either side without being wrapped up. Memorizing the full table is unnecessary if you keep one habit: when in doubt, add parentheses.
# Parentheses OVERRIDE precedence and also document your intent.
print((2 + 3) * 4) # 20, the parentheses force addition first
# Combining logic: comparisons group first, then 'and', then 'or'.
result = 0.97 >= 0.9 and 0.05 < 0.1 or False
print(result) # TrueThe logic line is worth tracing all the way through, because it uses three levels at once. Both comparisons evaluate first, giving True and True or False. Then and joins the two, since it outranks or and so takes both comparisons as its group, leaving True or False. Then or runs last, and the answer is True. Even when you know the precedence, parentheses make the intent obvious to the next reader, which is usually worth the few extra characters.
DON'T:
- DON'T trust your memory of precedence on a tricky line, wrap the part you mean with parentheses.
- DON'T assume left-to-right always wins,
2 + 3 * 4is14, because*outranks+regardless of position.
Type Conversion
What. Type conversion (also called casting) deliberately turns a value of one type into another using the built-in functions int(), float(), and str().
Why. Data rarely arrives in the type you need, a number read from a text file line by line arrives as a string, and you cannot do math on text. Conversion is the explicit step that makes the math possible. It is also how you format numbers back into text for output.
When. Convert whenever a value's type blocks the operation you need: string-to-number before arithmetic, number-to-string before joining into a message, float-to-int when you need a whole count.
int() produces an integer, float() produces a float, str() produces a string. Each takes a value and returns a new value of the target type, the original is unchanged.
# 'float("0.97")' parses the TEXT "0.97" into the number 0.97 so math is possible.
accuracy = float("0.97")
print(accuracy * 100) # 97.0
# 'int("42")' parses text into a whole number.
batch = int("42")
print(batch + 1) # 43
# 'str(...)' turns a number into text so it can be joined to other text.
report = "accuracy: " + str(0.97)
print(report) # accuracy: 0.97The str() in the report line is not optional decoration. Adding a number directly to a string is an error, not a silent coercion, so the conversion is what makes the join legal at all. This text-to-number-and-back cycle is the everyday reality of reading and reporting data: parse on the way in, format on the way out.
Two conversions need care. int() on a float truncates toward zero, it drops the decimal part, it does not round. And int() on a string that does not hold a whole number, such as "3.14", raises an error.
# 'int()' on a float TRUNCATES (chops the decimal), it does not round.
print(int(3.9)) # 3, NOT 4
print(int(-2.7)) # -2, truncated toward zero
# 'int("3.14")' ERRORS: int() cannot parse a decimal-looking string.
# print(int("3.14")) # ValueError: invalid literal for int() with base 10: '3.14'
# To go from a decimal string to an int, convert to float first, then to int.
print(int(float("3.14"))) # 3Read int(-2.7) giving -2 carefully, because "truncate" and "round down" part company on negative numbers. Truncation moves toward zero, so -2.7 becomes -2, while rounding down would have gone to -3. Floor division // is the one that genuinely rounds down, which is why -17 // 5 is -4 and not -3. Knowing these pitfalls saves hours of confusion when parsing real files.
DON'T:
- DON'T expect
int(3.9)to round to4, it truncates to3, useround()if you want rounding. - DON'T call
int("3.14")directly, it errors, convert withfloat()first thenint().
Lab Exercises
Both problems first, then both solutions. Do not peek.
Lab Exercise 1: Seven Operators and the Booleans They Answer
Covers the first half (Arithmetic Operators, Comparison Operators).
In the REPL, using the numbers 17 and 5, compute and print the first six arithmetic results in order: sum, difference, product, true division, floor division and modulo. Then compute 2 ** 5 to exercise the power operator. For each of the seven, state the result's type, int or float, and explain in one line why 17 / 5 and 17 // 5 differ.
Then write comparisons that produce each of these booleans and print them: is 0.95 greater than or equal to 0.9, is 64 not equal to 32, is 0.5 strictly less than 0.5, is 5 equal to 5.0. Finish by printing whether 0.1 + 0.2 == 0.3, and explain that answer in a comment.
Lab Exercise 2: Conditions, Order, and Text That Has to Become a Number
Covers the second half (Logical Operators, Operator Precedence, Type Conversion).
Create decisions.py. Bind accuracy = 0.93, loss = 0.08 and epochs_left = 0. Compute and print three booleans with clear names: is_accurate for accuracy at least 0.9, keep_training for loss above 0.05 and epochs still remaining, and should_stop for accuracy at least 0.9 or no epochs remaining. Then print 2 + 3 * 4 and (2 + 3) * 4 and explain in a comment why they differ.
In the same file, handle three fields that arrived from a text file as the strings "180", "20" and "4". Convert all three to integers, compute accuracy_parsed = correct / (correct + wrong) using 180 correct and 20 wrong, and print it. Build and print a report line by joining "folds: " to the fold count, which will require converting a number back into text. Finally, print int(3.9) and add a comment saying what you expected and what you got, and show in one more line how you would turn the string "3.14" into the integer 3, given that int("3.14") raises an error.
Solution 1: Seven Operators and the Booleans They Answer
# The first three on two integers stay integer.
print(17 + 5) # 22, int
print(17 - 5) # 12, int
print(17 * 5) # 85, int
# The three division operators, and note the type differences.
print(17 / 5) # 3.4, float, '/' ALWAYS returns a float
print(17 // 5) # 3, int, floor division rounds down
print(17 % 5) # 2, int, the remainder: 17 = 5*3 + 2
# The power operator, an int here because the exponent is not negative.
print(2 ** 5) # 32, int/ and // differ in exactly one respect: / keeps the fractional part and hands back a float, while // discards it and rounds down to an integer. Both are answering the same division, they just disagree about what to do with the 0.4 left over.
# '>=' is "greater than or equal".
print(0.95 >= 0.9) # True
# '!=' is "not equal".
print(64 != 32) # True
# '<' is STRICTLY less than, so equal values give False.
print(0.5 < 0.5) # False
# '==' compares VALUE across types, so 5 equals 5.0.
print(5 == 5.0) # True
# Float trap: 0.1 + 0.2 is stored as 0.30000000000000004, so this is False.
print(0.1 + 0.2 == 0.3) # FalseThe last line is the one to remember. == was perfectly willing to call an int and a float equal one line earlier, so it is not being fussy about types here, it is reporting honestly that the stored value really is not 0.3. That is why exact equality on computed floats is unreliable in a way that has nothing to do with the comparison operator.
DON'T:
- DON'T expect
17 / 5to be an integer,/always gives a float, here3.4. - DON'T confuse
//with rounding toward the nearest, it always rounds down, so-17 // 5is-4, not-3. - DON'T read
0.5 < 0.5asTrue, strict<excludes equality, you would need<=.
Solution 2: Conditions, Order, and Text That Has to Become a Number
accuracy = 0.93
loss = 0.08
epochs_left = 0
# A single comparison, producing a boolean.
is_accurate = accuracy >= 0.9
print(is_accurate) # True
# 'and' needs BOTH, and 'epochs_left > 0' is False, so the whole thing is False.
keep_training = loss > 0.05 and epochs_left > 0
print(keep_training) # False
# 'or' needs only ONE, and 'accuracy >= 0.9' is True, so the whole thing is True.
should_stop = accuracy >= 0.9 or epochs_left == 0
print(should_stop) # True
# Precedence: '*' before '+', then parentheses force the addition first instead.
print(2 + 3 * 4) # 14
print((2 + 3) * 4) # 20
# Fields as they arrive from a text file: all three are strings.
correct = int("180")
wrong = int("20")
folds = int("4")
# '/' returns a float, which is what an accuracy should be.
# 180 / 200
accuracy_parsed = correct / (correct + wrong)
print(accuracy_parsed) # 0.9
# 'str(folds)' turns the number back into text so '+' can join it to a string.
print("folds: " + str(folds)) # folds: 4
# 'int()' on a float TRUNCATES, it does not round to the nearest.
print(int(3.9)) # 3, not 4
# 'int("3.14")' raises ValueError, so route the decimal string through float first.
print(int(float("3.14"))) # 3Run it:
# Evaluate the conditions, show precedence, then parse the record.
python3 decisions.pykeep_training and should_stop both turn on epochs_left, and the operator decides what that one fact does: and fails as soon as one side fails, so epochs_left > 0 being False sinks keep_training, while or succeeds as soon as one side succeeds, so accuracy >= 0.9 alone carries should_stop. The precedence pair differs because * outranks +, so 2 + 3 * 4 computes 3 * 4 first and reaches 14, while the parentheses in (2 + 3) * 4 force the addition and reach 20.
The parsing half is the shape of nearly every real data script. Text comes in, it will not do arithmetic, so you convert it deliberately, do the math, and convert back to text to report it. The int(float("3.14")) detour exists because int() refuses a decimal point in a string outright, and refusing loudly is better than the alternative, since a silent guess about what you meant would be worse.
DON'T:
- DON'T write
loss > 0.05 and > 0, each side ofandneeds a complete expression, hereepochs_left > 0. - DON'T assume
2 + 3 * 4reads left to right,*binds tighter, so it is14and not20. - DON'T write
"folds: " + folds, joining text to a number is an error, the number must bestr()first.
Theory Assessment
Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.
Q1: Three Kinds of Division, and One Wrong Symbol
Two parts on arithmetic. First, explain the difference between /, // and % using 17 and 5, stating the type each returns. Second, a student writes 2 ^ 10 expecting 1024 and gets a completely different number without any error appearing, explain what ^ actually did and give the correct operator.
Hint: For the first part, one always gives a float, one rounds down to a whole number, and one gives the leftover. For the second, Python reserves ^ for something unrelated to exponentiation, and the danger is that it succeeds rather than complains.
Q2: One Equals Sign, Two Equals Signs, and Two Words
Two parts on producing and combining booleans. First, explain the difference between x = 5 and x == 5, and say what each one produces. Second, given accuracy = 0.8 and loss = 0.2, evaluate both accuracy >= 0.9 and loss < 0.5 and accuracy >= 0.9 or loss < 0.5, and explain why they differ.
Hint: For the first, one performs an action and yields nothing usable while the other asks a question and yields a boolean. For the second, and needs both sides true and or needs only one.
Q3: Predict the Order, Then Make It Explicit
Two parts on precedence. First, without running it, predict 10 - 2 * 3 ** 2 and explain the order in which the three operators apply. Second, rewrite 2 + 3 > 4 and 1 < 0 with parentheses that make its evaluation order explicit, and give the final boolean.
Hint: Apply the highest precedence first, **, then *, then -. For the second, arithmetic groups first, then the comparisons, then and last of all.
Q4: Two Ways Down Are Not the Same Way Down
A field arrives from a file as the string "3.99" and you need it as a whole number. int("3.99") raises an error, so a student tries two repairs: int(float("3.99")) gives 3, and round(float("3.99")) gives 4.
Answer three things. First, why does int("3.99") fail while float("3.99") succeeds? Second, why do the two repairs disagree, and which one is "correct"? Third, and this is the part that catches people: the lesson said // rounds down and int() chops the decimal, which sound like the same operation. Show with one negative number that they are not the same operation, and say which of the two actually rounds down.
Hint: For the third part, try both on a negative value and watch them separate. Ask yourself which direction "down" points on a number line, and whether "chopping the decimal" moves a negative number in that same direction.
Answer 1:
For 17 and 5: 17 / 5 is true division and returns 3.4, a float, and it returns a float always, even when the division is clean. 17 // 5 is floor division and returns 3, an int, the result rounded down to a whole number. 17 % 5 is modulo and returns 2, an int, the remainder left over after that floor division. Together // and % answer "how many whole times does 5 fit, and what is left".
2 ^ 10 does not mean exponentiation in Python. On two integers ^ is the bitwise XOR operator, which combines them digit by digit in binary, so the expression evaluates to 8, and crucially it does so without raising anything, which is why the mistake survives into results rather than being caught. The correct operator for "2 to the power 10" is **, and 2 ** 10 returns 1024.
DON'T call / "integer division", that is //, and / always gives a float. And DON'T carry the math-class meaning of ^ into Python, a wrong answer that runs cleanly is worse than an error.
Answer 2:
x = 5 is an assignment statement: it performs the action of binding the name x to the value 5, and it yields no usable value. x == 5 is a comparison expression: it asks "does x equal 5?" and produces a boolean, True or False. One equals sign acts, two equals signs ask.
With accuracy = 0.8 and loss = 0.2, the first compound condition is False, because accuracy >= 0.9 is False and and requires both sides to hold. The second is True, because loss < 0.5 is True and or requires only one. They differ because and is strict where or is lenient, and here exactly one of the two component facts happens to be true.
DON'T use = where you mean to compare. And DON'T assume a compound condition is true just because one part of it is, with and, every part must hold.
Answer 3:
# '**' first: 3 ** 2 = 9. Then '*': 2 * 9 = 18. Then '-': 10 - 18 = -8.
print(10 - 2 * 3 ** 2) # -8The result is -8. Precedence applies ** first, giving 9, then *, giving 18, then - last, giving -8. The subtraction is applied last despite appearing leftmost, because it has the lowest precedence of the three.
# Arithmetic first, then each comparison, then 'and' joins them.
((2 + 3) > 4) and (1 < 0) # (5 > 4) and (1 < 0) -> True and False -> FalseThe explicit form is ((2 + 3) > 4) and (1 < 0), and the final boolean is False. The addition runs first, then the two comparisons resolve to True and False, and finally and combines them into False.
DON'T apply the operators left to right, and DON'T let the bare expression mislead you, and binds last, joining the two whole comparisons.
Answer 4:
int("3.99") raises a ValueError because int() parses only strings that look like whole numbers, and the decimal point is text it will not interpret. float("3.99") succeeds because float() does understand decimal-looking text, returning the float 3.99.
The two repairs disagree because they are different operations, not two spellings of one. int(float("3.99")) truncates the float, chopping the decimal part to give 3. round(float("3.99")) rounds to the nearest whole number, giving 4. Neither is "correct" in general, and the question is which one you meant: use truncation when you want how many whole units fit, and rounding when you want the nearest value. Reaching for int() while thinking "round" is how a 3.99 silently becomes a 3.
On the third part, // and int() are genuinely different operations, and a negative result with a fractional part separates them. -17 // 5 is -4, while int(-17 / 5) is int(-3.4), which is -3. // is the one that actually rounds down, always moving toward the more negative value, while int() truncates toward zero, which moves a negative number up. On positive numbers the two directions coincide, which is exactly why the difference stays hidden until a negative value turns up and the answers separate by one.
DON'T treat "truncate" and "round down" as synonyms, they agree on positives and can disagree on negatives, and that is precisely the kind of bug that survives every test written with positive numbers.
Assignment
One assignment. The problem and its hints first, then the solution.
Assignment: The CSV Field Parser
A row read from a CSV file arrives as three strings: correct_str = "434", total_str = "500" and threshold_str = "0.85". Create csv_parser.py that works that row all the way through to a decision.
(1) Convert correct_str and total_str to integers and threshold_str to a float. (2) Compute accuracy = correct / total as a float and print it. (3) Build and print a report line using str(), in the shape "accuracy: " + str(accuracy). (4) Use a chained comparison, 0.0 <= accuracy <= 1.0, which Python reads as 0.0 <= accuracy and accuracy <= 1.0, to verify the range in a single expression. (5) Decide passed = accuracy >= threshold and print it. (6) Use round(accuracy, 2) to print the accuracy to two decimal places. (7) Use divmod(correct, 100) to report how many full hundreds of correct predictions there were, and the remainder.
In comments, settle three things: why every field had to be converted before any math, why int("0.85") would have failed where float("0.85") succeeds, and what int(accuracy * 100) would print as a percentage against round(accuracy * 100), along with which of the two you would want for a reported percentage.
Hints:
- Text read from a file arrives as strings, so every numeric field needs
int()orfloat()before arithmetic, andint()refuses a decimal point outright rather than guessing what you meant by it. round(x, 2)keeps two decimal places anddivmod(a, b)returns the(quotient, remainder)pair in one call, which is the same two answers//and%give separately.
Solution:
# Fields as they arrive from the CSV: all three are strings.
correct_str = "434"
total_str = "500"
threshold_str = "0.85"
# (1) Convert before any math: int() for whole counts, float() for the decimal.
correct = int(correct_str) # 434
total = int(total_str) # 500
threshold = float(threshold_str) # 0.85
# (2) Compute accuracy, '/' gives a float.
accuracy = correct / total # 434 / 500
print(accuracy) # 0.868
# (3) Build a report, str() turns the number into joinable text.
print("accuracy: " + str(accuracy)) # accuracy: 0.868
# (4) Chained comparison validates the range in one readable expression.
print(0.0 <= accuracy <= 1.0) # True
# (5) Pass or fail against the parsed threshold.
passed = accuracy >= threshold # 0.868 >= 0.85
print(passed) # True
# (6) round(x, 2) keeps two decimal places, and it rounds rather than chopping.
print(round(accuracy, 2)) # 0.87
# (7) divmod gives full hundreds of correct predictions and the leftover.
print(divmod(correct, 100)) # (4, 34), since 434 = 4*100 + 34
# The file gave every field as TEXT, and text does no arithmetic, so each numeric
# field had to be converted first. 'int("0.85")' would raise ValueError because
# int() will not parse a decimal point, which is exactly why threshold used float().
# 'int(accuracy * 100)' would print 86, because it truncates 86.8 toward zero,
# where round(accuracy * 100) gives 87. For a reported percentage you want
# the rounded one, since truncation reports a worse result than was achieved.Run it:
# Parse the CSV fields, compute, validate, and report.
python3 csv_parser.pyThe parser converts the three text fields and everything downstream then works: accuracy is 0.868, the report joins cleanly through str(), the chained comparison confirms the value sits inside [0.0, 1.0], and passed is True because 0.868 clears the 0.85 threshold.
Steps (6) and (7) are where the assignment earns its place. round(accuracy, 2) reports 0.87, rounding 0.868 up, where truncating would have chopped it down to 0.86, and the same split decides the percentage question in the comment: int(accuracy * 100) is 86 while round(accuracy * 100) gives 87. One point of accuracy is not a rounding detail when it is the number on a report. And divmod(434, 100) returning (4, 34) is // and % answering together, the same pair from the first subtitle of this lesson, which is why it needs no new explanation to read.
DON'T skip the conversions and try correct_str / total_str, dividing two strings is an error, and the int() and float() casts are mandatory before any arithmetic. And DON'T reach for int() when you mean to round, it truncates, and on a reported metric that difference is visible to whoever reads the number.
Additional Resources
Optional, and nothing that follows depends on either one. Open them if this lesson left you wanting the exact rules rather than the working ones.
- YouTube - "Python Tutorial for Beginners" by freeCodeCamp.org. The "Numbers" chapter, for a second visual pass over arithmetic.
- Official docs - Built-in Functions, the
int,floatandstrentries. The exact conversion rules, including precisely which inputs each one accepts and which it rejects.
Summary
This lesson took the inert values of the last three lessons and finally let them interact. Operators are nothing more than small machines: feed in one or two values, get one value out. Arithmetic operators turn numbers into numbers, comparison operators turn pairs of values into the booleans you met earlier, and logical operators weave those booleans into the compound conditions that real decisions require. Each family is small, but together they are the entire vocabulary of computation, every calculation and every choice your programs will ever make is assembled from these symbols.
Two threads ran underneath the operators and will keep surfacing for the rest of the course. The first is precedence: a mixed expression is grouped by a fixed pecking order rather than left to right, and the cheapest insurance against a silent bug is a pair of parentheses around the part you care about. The second is type conversion, which is less glamorous but more practical. Data often arrives as text rather than in the type you want, from files, from inputs, from the outside world, and the deliberate int(), float(), and str() calls you practiced are the bridge between that raw text and the math you need to do with it.
That bridge is where this whole section has been heading. A number read out of a text file line by line shows up as a string first, and the conversion you now perform on purpose is the unglamorous step that makes every later computation possible. The grand pipelines ahead, loading a dataset, cleaning it, computing a metric, comparing it to a threshold, deciding whether a model is good enough, are built almost entirely from what you assembled here: parse the text into numbers, combine them with operators, compare the result, and let a boolean drive the decision. You now have the full set of moves for making values interact, and that is the last primitive you needed before code starts to make choices on its own.
Cheat Sheet
Part A - Reference Table
| Operator(s) | Meaning | Example | Result / Notes |
|---|---|---|---|
+ - * ** |
add, sub, mul, power | 2 ** 10 |
1024, int for int operands unless the power is negative |
/ |
true division | 10 / 2 |
5.0, always a float |
// |
floor division | 10 // 3 |
3, rounds down |
% |
modulo (remainder) | 10 % 3 |
1 |
== != < > <= >= |
comparison | 0.97 > 0.9 |
a boolean (True/False) |
and or not |
logical | a >= 0.9 and b < 0.1 |
combine/invert booleans |
int() float() str() |
conversion | float("0.97") |
new value of target type |
Part B - Compact Code Block
17 / 5 # 3.4 true division -> float
17 // 5 # 3 floor division -> int
17 % 5 # 2 remainder
2 ** 10 # 1024 power (NOT ^)
0.97 >= 0.9 # True comparison makes a boolean
a >= 0.9 and b < 0.1 # combine with and/or/not
2 + 3 * 4 # 14 '*' before '+', use () to override
float("0.97") # 0.97 text -> number (math now possible)
int(3.9) # 3 truncates, does NOT round
str(0.97) # '0.97' number -> text for joining
# int("3.14") errors -> use int(float("3.14"))