Simple interest
Simple interest pays the same amount every year, worked out on the original sum only. Interest does not earn interest; that is compounding, which module 1.4 covers. Money markets use simple interest for loans and deposits shorter than a year.
Rule: Simple interest
is the principal, the sum borrowed or deposited. is the yearly rate as a decimal and the time in years. is the interest and the amount repaid at the end.
For the loan in the opening: , so .
One formula, four questions
links four quantities. Know any three and you can find the fourth by making it the subject. Here is the rate, using the gather-and-divide method from lesson 0.3.2:
- 1
- 2
- 3
The same moves give the other two. Dividing by gives the present value, what a future sum is worth today. Dividing by gives the time.
Try it
SymPy rearranges the formula for each letter in turn, which checks all three results at once. It writes as r*t + 1.
The second half puts in numbers. $5,900 due in 3 years at 6% is worth $5,000 today. $5,000 that grows to $5,600 in 2 years earned 6% a year.
Simple interest, rearranged
Write three functions, one for each rearrangement of :
present_value(amount, rate, years)returns .implied_rate(principal, amount, years)returns as a decimal.years_needed(principal, amount, rate)returns .
Work out each formula on paper first, or check them with SymPy in the cell above.
See the tests
def test_present_value():
"""$5,900 due in 3 years at 6% is worth $5,000 today"""
expect_close(present_value(5900, 0.06, 3), 5000.0, what="present_value(5900, 0.06, 3)")
def test_present_value_part_year():
"""$10,125 due in a quarter of a year at 5% is worth $10,000 today"""
expect_close(present_value(10125, 0.05, 0.25), 10000.0, what="present_value(10125, 0.05, 0.25)")
def test_implied_rate():
"""$5,000 that grows to $5,600 in 2 years earned 6% a year"""
expect_close(implied_rate(5000, 5600, 2), 0.06, what="implied_rate(5000, 5600, 2)")
def test_years_needed():
"""$5,000 takes 10 years to reach $7,000 at 4%"""
expect_close(years_needed(5000, 7000, 0.04), 10.0, what="years_needed(5000, 7000, 0.04)")
def test_round_trip():
"""Each answer leads back to the amount: P(1 + rt) = A"""
p, a, t = 2500, 2800, 1.5
r = implied_rate(p, a, t)
expect_close(p * (1 + r * t), a, what="P(1 + rt) with your implied_rate")
expect_close(present_value(a, r, t), p, what="present_value with that rate")
expect_close(years_needed(p, a, r), t, what="years_needed with that rate")
Break-even
Now the coffee cart. Each cup sells for a price and costs to make: coffee, milk and the cup. This is the variable cost, because it grows with sales. The cart also has a fixed cost that must be paid whatever it sells: the licence, the cart itself and insurance.
Selling cups brings in and costs , so the profit is
Each cup adds to profit. That amount, the contribution margin, chips away at the fixed cost. For the cart, and , so each cup contributes $1.80.
The cart breaks even when profit is no longer negative. That is an inequality, solved as in lesson 0.3.3:
The last step divides by . That is positive when the price is above the variable cost, so the sign does not flip. If , every cup loses money and the cart never breaks even.
Try it
For the cart, . You cannot sell part of a cup, so round up: the cart needs 6,667 cups.
The cell checks the two whole numbers either side. At 6,666 cups the cart is still $1.20 short; at 6,667 it is 60 cents ahead.
Try it
On a graph, break-even is where the revenue line crosses the cost line . Left of the crossing the cart loses money; right of it, it makes money.
To aim for a profit target instead of zero, solve , which gives . A $6,000 profit needs cups.
Break-even units
Write break_even_units(fixed, price, variable) that returns the smallest whole number of units whose profit is zero or more.
- Use and round up with
math.ceil. - If
price <= variable, the business can never break even. RaiseValueError.
The tests check that profit is non-negative at your answer and negative one unit below it.
See the tests
def _profit(q, fixed, price, variable):
return (price - variable) * q - fixed
def _raises_value_error(*args):
try:
break_even_units(*args)
except ValueError:
return True
return False
def test_coffee_cart():
"""The coffee cart (fixed $12,000, price $3.50, cost $1.70) needs 6,667 cups"""
expect_equal(break_even_units(12_000, 3.50, 1.70), 6667, what="break_even_units(12000, 3.50, 1.70)")
def test_exact_division():
"""When the margin divides the fixed cost exactly, no rounding: 1000 / (15 − 5) = 100"""
expect_equal(break_even_units(1000, 15, 5), 100, what="break_even_units(1000, 15, 5)")
def test_profit_either_side():
"""Profit is at least 0 at the answer and below 0 one unit earlier"""
args = (7_500, 24.0, 13.0)
q = break_even_units(*args)
assert _profit(q, *args) >= 0, f"profit at your answer {q} is {_profit(q, *args):.2f}, which is negative"
assert _profit(q - 1, *args) < 0, f"profit at {q - 1} units is already {_profit(q - 1, *args):.2f}, so {q} is not the smallest"
def test_whole_number():
"""The answer is a whole number of units"""
q = break_even_units(12_000, 3.50, 1.70)
assert isinstance(q, int), f"expected a whole number (int), got {q!r}"
def test_no_fixed_cost():
"""With no fixed cost, zero units already break even"""
expect_equal(break_even_units(0, 5, 2), 0, what="break_even_units(0, 5, 2)")
def test_never_breaks_even():
"""A price at or below the variable cost never breaks even, so raise ValueError"""
assert _raises_value_error(1000, 2.0, 2.5), "expected ValueError when price < variable"
assert _raises_value_error(1000, 3.0, 3.0), "expected ValueError when price == variable"
Summary card
- Simple interest: and . Rearranged: , , .
sp.solve(sp.Eq(left, right), letter)rearranges a formula for any letter, and checks your algebra.- Profit on units is . Break-even is , rounded up; it exists only if .
- For a profit target : .