Try each task in your workspace first. Open a solution only when your code runs, or when you have been stuck for a while. Your solution can be different and still right: compare what it prints. The reading tasks and their answers are on the Questions page.
Writing tasks
Task 6 · a purchase with a rule
You hold 2 VOO at 512.00 CHF and have 3000.00 CHF cash. Rule: spend at most 40 % of your cash, and buy whole shares only. Write the lines that work out how many shares you buy, update shares and cash, check that wealth did not change (with a tolerance, not ==), and print one line with an f-string.
Show the solution
ticker = "VOO"
shares = 2
price = 512.0
cash = 3000.0
wealth_before = shares * price + cash
bought = int(cash * 0.4 // price)
shares += bought
cash -= bought * price
print(f"{ticker}: bought {bought}, now {shares} shares, cash {cash:.2f} CHF")
print(abs(shares * price + cash - wealth_before) < 0.01)
It prints:
VOO: bought 2, now 4 shares, cash 1976.00 CHF
True
40 % of 3000 is 1200; 1200 // 512 is 2 whole shares. Update shares and cash from the same bought, so the invariant holds. int() turns the float result of // into a whole number for printing.
Task 7 · the same purchase in cents
Floats cannot store most prices exactly. Money is safer as whole cents. Redo Task 6 with integers only: 512.00 CHF is 51200 cents, 3000.00 CHF is 300000 cents. At the end, print the cash as CHF, built from // and % (no division, no float).
Show the solution
shares = 2
price_c = 51200
cash_c = 300000
bought = cash_c * 40 // 100 // price_c
shares += bought
cash_c -= bought * price_c
print(f"CHF {cash_c // 100}.{cash_c % 100:02d}")
It prints:
CHF 1976.00
cash_c // 100 is the francs, cash_c % 100 the cents; :02d pads 0 to "00". Everything stays an int, so nothing is ever rounded. Choosing the representation of the data is a decision the programmer makes.
Task 8 · the fee in cents, rounded half up
Back to Task 5's fee: 0.25 % of 1070.00 CHF. Compute it in cents with integers only, rounding half up (2.675 → 2.68), and print it as CHF with // and %. Hint: 1070.00 CHF is 107000 cents, and 0.25 % is 25 / 10000. Adding half of the divisor before // rounds half up.
Show the solution
trade_c = 107000
fee_c = (trade_c * 25 + 5000) // 10000
print(f"fee CHF {fee_c // 100}.{fee_c % 100:02d}")
It prints:
fee CHF 2.68
The exact fee is 107000 × 25 / 10000 = 267.5 cents. Floor division alone would give 267 (round down); adding 5000, half of the divisor, first turns it into round half up: 268. No float anywhere, so the answer cannot depend on how it was computed.
Task 9 · split the cash to the last cent
Split 1000.00 CHF between 3 accounts. Every account gets whole cents, and the three parts must add up to exactly 1000.00: no cent lost, no cent invented. Use cents, // and %. The first account takes the leftover cent. Then explain: why does round(1000.00 / 3, 2) * 3 not give 1000.00?
Show the solution
total_c = 100000
each = total_c // 3
first = each + total_c % 3
print(f"{first // 100}.{first % 100:02d} {each // 100}.{each % 100:02d} {each // 100}.{each % 100:02d}")
print(first + each + each == total_c)
print(round(1000.00 / 3, 2) * 3)
It prints:
333.34 333.33 333.33
True
999.99
// gives the equal share, % gives what is left over; somebody must receive the leftover cent, and the code has to say who. Rounding each share to 333.33 and multiplying back loses the cent: 999.99.
Task 10 · swap two prices
The two lines below try to swap the prices. Run them. What went wrong, and why (think of Task 1)? Fix it so that the print shows 102.3 190.5.
Show the solution
price_a = 190.5
price_b = 102.3
tmp = price_a
price_a = price_b
price_b = tmp
print("Task 10:", price_a, price_b)
It prints:
Task 10: 102.3 190.5
After price_a = price_b both names are labels on 102.3; nothing remembers 190.5 any more. Keep it under a third name first.
★ Harder: classic puzzles
★ Task 11 · Seconds to a clock time
A trade is stamped with the number of seconds since midnight: 34567. Print it as HH:MM:SS, with two digits each. Use // and % only.
Show the solution
t = 34567
hours = t // 3600
minutes = t % 3600 // 60
seconds = t % 60
print(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
It prints:
09:36:07
3600 seconds in an hour: // gives the hours, % the rest. The rest // 60 gives the minutes; t % 60 the seconds. :02d pads each part to two digits.
★ Task 12 · Reverse a 4-digit number
Reverse the digits of a 4-digit account number using // and % only (no strings): 1234 becomes 4321. Test it on 1234 and on 5071. What happens to the 0 in 5071, and why?
Show the solution
for n in (1234, 5071):
d1 = n // 1000
d2 = n // 100 % 10
d3 = n // 10 % 10
d4 = n % 10
print(n, "->", d4 * 1000 + d3 * 100 + d2 * 10 + d1)
It prints:
1234 -> 4321
5071 -> 1705
n % 10 is the last digit; n // 10 drops it. Each digit is peeled off with // and %, then the number is rebuilt with place values. 5071 reversed is 1705: the 0 moves to the hundreds place and survives, because we rebuild a number, not a string.
★ Task 13 · Leap years in one expression
Bond interest counts 366 days in a leap year. A year is a leap year if it divides by 4, except years that divide by 100, except again years that divide by 400. Write ONE boolean expression with and / or / not / % and print it for 2024, 2100, 2000 and 2026.
Show the solution
for year in (2024, 2100, 2000, 2026):
is_leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
print(year, is_leap)
It prints:
2024 True
2100 False
2000 True
2026 False
Read the rule as: divisible by 4 AND (not a century OR a multiple of 400). The parentheses matter: and binds tighter than or, so without them 2100 would count as a leap year.
Extra practice
Practice 1 · Compound interest, yearly or monthly
1000.00 CHF earns 4 % a year for 10 years. Compute the final value twice: once compounded every year (4 % once a year), once compounded every month (4 % / 12, twelve times a year). Print both with 2 decimals. Which is larger, and why?
Show the solution
principal = 1000.0
yearly = principal * 1.04 ** 10
monthly = principal * (1 + 0.04 / 12) ** 120
print(f"{yearly:.2f} {monthly:.2f}")
It prints:
1480.24 1490.83
** does the repeated multiplication. Monthly compounding pays interest on interest earlier, so it ends higher. Watch the precedence: (1 + 0.04 / 12) needs its parentheses.
Practice 2 · Sum of the digits
Print the sum of the digits of the 4-digit number 2026, using // and % only (no strings). Then try 1234 and 9999.
Show the solution
n = 2026
print(n // 1000 + n // 100 % 10 + n // 10 % 10 + n % 10)
It prints:
10
The same digit peeling as ★ Task 12: // drops digits from the right, % 10 keeps the last one.
Practice 3 · Pay 1976.40 CHF in banknotes
Pay 1976.40 CHF with as few Swiss banknotes as possible (1000, 200, 100, 50, 20, 10) and say what is left for coins. Work in cents with // and %: take as many of the largest note as fit, keep the rest, go to the next note.
Show the solution
c = 197640
n1000 = c // 100000
c = c % 100000
n200 = c // 20000
c = c % 20000
n100 = c // 10000
c = c % 10000
n50 = c // 5000
c = c % 5000
n20 = c // 2000
c = c % 2000
n10 = c // 1000
c = c % 1000
print(n1000, n200, n100, n50, n20, n10, f"coins {c // 100}.{c % 100:02d}")
It prints:
1 4 1 1 1 0 coins 6.40
The greedy change-making algorithm: // says how many notes fit, % says what is left. With Swiss denominations taking the largest note first is always optimal. Next week a loop over a list of notes will replace the six copies.