Before we begin a warning. The following section, calculations, etc. may or may not be accurate and are only meant as a programming exercise, not a financial advice.
As a prelude let’s write a helper function that will nicely display the amount of money we get and improve the readability of our results.
function getFormattedMoney(money::Real, sep::Char=',')::Str
@assert money >= 0 "money must be >= 0"
amount::Str = round(Int, money) |> string
result::Str = ""
counter::Int = 0
for digit in reverse(amount) # digit is a single digit (type Char)
if counter == 3
result = sep * result
counter = 0
end
result = digit * result
counter += 1
end
return result * " USD"
end
The function may not be the most performant, but it was pretty easy to write. It receives money as a Real number and sets apart every three digits with a separator (sep
) of our choice. Since in English a decimal separator is .
(dot) and a thousand separator is ,
(comma) then that’s what we used here as our default (sep::Char=','
). Inside our formatter we round the number to integer (round(Int, money)
) and convert it to string
. Next we traverse all the digits (for digit
) in the opposite direction (from right to left) thanks to the reverse(amount)
. Every third digit (if counter == 3
) we place our sep
to the result
and count to three a new (counter = 0
). Besides, we prepend our digit
to the result
(digit * result
) and increase the counter (counter +=1
). In the end we return the formatted number (result * " USD"
).
Let’s see how it works.
getFormattedMoney(12345.06)
12,345 USD
I think that twelve thousand three hundred and forty five is much easier to process this way than in its alternative transcription form (12345
). Still, be aware of the rounding that takes place in it.
Before we begin a quick refresher on percentages. As explained, e.g. here per definition a percentage is a hundredth part of the whole and can be represented in a few equivalent forms, i.e.
For calculations it is especially useful to use them as decimals, hence we will often divide a percentage by one hundred. Now, let’s say that I got $100 (this initial sum of money is called the principal) on a deposit that pays 5% interest yearly. That means that after one year I will get $105 or 105% of my initial principal (the 5% of $100 is $5, and it is the interest we get for giving our money to the bank). I can express this mathematically as $100 * 105% = $105
or to make it easier to type with a calculator 100 * 1.05 = 105
. If the deposit lasted two years then I would have to repeat the process one more time for year number two, i.e. $105 * 105% = $110.25
(105% of my new principal), also to be expressed as 105 * 1.05 = 110.25
. Since as we said the $105
is actually 100 * 1.05
then I can rewrite it as (100 * 1.05) * 1.05 = 110.25
. For year number three I got $110.25 * 105% = $115.7625
or 110.25 * 1.05 = 115.7625
or ((100 * 1.05) * 1.05) * 1.05 = 115.7625
. The parenthesis are there to signify boundaries of mathematical operations for a previous year. We can get rid of them to get: 100 * 1.05 * 1.05 * 1.05
(we multiply 1.05
by itself as many times as there are years). Hence a pattern emerges:
\(deposit\ value = \$100 * 1.05^{number\ of\ years}\)
or more generally (remember about the order of mathematical operations):
\(total\ value = initial\ principal * (1 + percentage/100)^{number\ of\ years}\)
Let’s put that into a Julia’s function.
function getValue(principal::Real, avgPercentage::Real, years::Int)::Flt
@assert years > 0 "years must be greater than 0"
@assert principal > 0 "principal must be greater than 0"
return principal * (1+avgPercentage/100)^years
end
And test it on our previous example.
round.([getValue(100, 5, i) for i in 1:3], digits=4)
[105.0, 110.25, 115.7625]
Now we are ready to see if Fry is a billionaire.
# Futurama s1e6: 93 cents ($0.93), 2.25%, 1_000 years
frysMoney = getValue(0.93, 2.25, 1_000)
frysMoney |> getFormattedMoney
4,283,508,450 USD
And indeed he is (four billion two hundred eighty three million, etc.). Still, before you head towards cryogenic clinic to duplicate Fry’s success be aware of all the issues with cryonics and that most of the people frozen in 1960s’ are not preserved today. Therefore, the profit seems to be purely theoretical.
Anyway, as said the function could be used to estimate the nominal value that you will get from your deposit (with a stable yearly interest rate). You could also look at it from the other end and estimate how much you would have to earn to cover up for an average inflation rate over a few years. Keep that in mind as we go to the solution for Question 2 (see Section 13.1.2).
What about Question 2. If on December 31, 2019 my monthly salary was $10,000 then how much I would have to earn in January 2025 to maintain my purchasing power given that the inflation rates for years 2020-2024 were equal: 3.4%, 5.1%, 14.4%, 11.4%, and 3.6%. To answer that we will use the same reasoning as in the previous section (Section 13.2.1), but since the percentages differ from year to year we will use a for
loop to cover for that.
function getValue(principal::Real, percentages::Vec{<:Real})::Flt
@assert principal > 0 "principal must be greater than 0"
for p in percentages
principal *= 1 + (p / 100)
end
return principal
end
Second getValue
method defined, time to put it into good use.
money2019 = 10_000 # Dec 31, 2019
inflPoland = [3.4, 5.1, 14.4, 11.4, 3.6] # yrs: 2020-2024
money2025infl = getValue(money2019, inflPoland) # Jan 1, 2025
(
getFormattedMoney(money2019),
getFormattedMoney(money2025infl)
)
("10,000 USD", "14,348 USD")
So I would need to earn roughly $14,348 to be able to buy the same amount of goods in 2025 as I could with $10,000 in 2019. Not sure why, but that doesn’t put a smile on my face.
As a final step let’s think was it worth a while to open a 5 years long bank deposit (6% yearly interest rate) on January 1, 2020 given the inflation rates discussed in the previous section (Section 13.2.2). Would I make any real profit on January 1, 2025?
In order to figure that out we need to counterbalance two factors. The increase in the nominal value that we get due to the interest rate and a drop in the real value of money due to the inflation rate. In other words, we might want to calculate the real percentage change in money value given the two factors combined. For that we should either implement a suitable formula obtained from a reliable source or come up with the one ourselves. In order to learn we will try the latter approach (no pain, no gain).
To that end we will use proportions (I believe they taught me that in the high school in chemistry, so bear with me, you can do it) and some simple imaginable example. To make sure we are on the same page let’s talk briefly about the proportions.
Imagine that for $10 (usd
below) we can buy 2 bread loafs (bl
below). If so, then how much bread can we buy for $5? That’s easy, one bread loaf. You can solve it with proportions like so:
\[10\ usd - 2\ bl\qquad{(9)}\]
\[5\ usd - x\ bl\qquad{(10)}\]
We set the same units on the same sides (left - right). Next, in order to get \(x\) we multiply the numbers on the diagonals: 5 * 2 goes to the numerator and 10 goes into denominator (since it got no pair on the diagonal it falls to the bottom):
\(x = \frac{5\ usd\ *\ 2\ bl}{10\ usd}\)
Then we forward to our solution.
\(x = \frac{5 * 2}{10} = \frac{10}{10} = 1\)
This works because Equation 9 and Equation 10 could be rewritten as:
\(\frac{10}{2} = \frac{5}{x}\)
or
\(\frac{2}{10} = \frac{x}{5}\)
The above is basically just finding an equivalent fraction that we learned in our primary schools. Still, I like and remember the proportions example better.
With that under our belt let’s follow with a simple example. Imagine that in this year for $100 (usd
below) we can buy 100 chocolate bars (cb
below). Due to the yearly inflation that is 2% the same 100 chocolate bars will cost $102 after a year. Luckily, thanks to the 5% yearly interest rate we will have $105 in banknotes from our deposit. So how many chocolate bars will we be able to buy after a year?
That’s easy thanks to the proportions.
\[102\ usd - 100\ cb\qquad{(11)}\]
\[105\ usd - x\ cb\qquad{(12)}\]
Which gives us:
\(x = \frac{105\ usd\ * 100\ cb}{102\ usd}\)
and
\(x = \frac{105 * 100}{102} = calculator\ does\ pip,\ pip,\ ...\ \approx 102.94\)
Therefore, we can see that in this scenario the $105 in banknotes will allow us to buy 102.94 chocolate bars (we think of it as a stable product that by itself does not gain or loose value over time). Based on the chocolate bars we can evaluate the real gain/loss of our nominal money to be: 102.94 - 100 = 2.94 chocolate bar
or 2.94% (chocolate bars were just an abstraction needed as a reference point, something that does not change its value over time, hence a constant). When we put it all together (starting from Equation 11) in a formula, we get (notice that the 100 below is a constant that we refered to in the sentence before):
\[real\ percentage = \frac{105\ usd\ *\ 100}{102\ usd} - 100\qquad{(13)}\]
Just like the chocolate bars also the dollars are a placeholder in our example. We used them because the more material and concrete the objects are the easier it is to think about them and manipulate them in our heads. Notice that 105 usd
in Equation 13 and Equation 12 actually stands for percentage gain in value (100 + inflation rate
) of our money (the 105% that we used in our explanation in the calculations in Section 13.2.1). On the other hand, 102 usd
in Equation 13 and Equation 11 is actually a placeholder for percentage change in value due to the inflation (we divide by it, so we decrease our gain in numerator by it). Therefore we can rewrite Equation 13 to:
\[real\ percentage = \frac{(100\ +\ interest\ rate) * 100}{100\ +\ inflation\ rate} - 100\qquad{(14)}\]
Now let’s put Equation 14 into a Julia’s function.
function getRealPercChange(interestPerc::Real, inflPerc::Real)::Flt
return ((100 + interestPerc) * 100) / (100 + inflPerc) - 100
end
Note: As a practical exercise you may further simplify the formula in Equation 14 using a pen and paper. Compare your result with the output of Symbolics.jl (that we met in Section 3.2), e.g.
Sym.@variables realPerc interPerc inflPerc
andSym.simplify(realPerc ~ ((100 + interPerc)*100)/(100 + inflPerc)-100)
. Of course, if you think that’s too much mathematics for one day, then don`t. Julia won’t mind computing the result of Equation 14 for you.
Now we can use it to write our final, third method, for getValue
.
function getValue(principal::Real,
interestPercs::Vec{<:Real},
inflationPercs::Vec{<:Real})::Flt
@assert principal > 0 "principal must be greater than 0"
@assert length(interestPercs) == length(inflationPercs)
"interestPercs and inflationPercs must be of equal lenghts"
for (intr, infl) in zip(interestPercs, inflationPercs)
principal = getValue(principal, getRealPercChange(intr, infl), 1)
end
return principal
end
We will use it to answer our question.
interestDeposit = 6 # yrs: 2020-2025
numYrs = length(inflPoland)
money2025deposit = getValue(money2019,
interestDeposit, numYrs) # Jan 1, 2025
money2025depositInflation = getValue(
money2019,
repeat([interestDeposit], numYrs), inflPoland) # Jan 1, 2025
(
getFormattedMoney(money2019),
getFormattedMoney(money2025deposit),
getFormattedMoney(money2025depositInflation)
)
("10,000 USD", "13,382 USD", "9,327 USD")
And again, reality turns out to be disappointing. The initial principal of $10,000 (January 1, 2020) was increased by 6% yearly which gave me $13,382 in banknotes on January 1, 2025 for which I can buy the same amount of goods that I could for $9,327 on January 1, 2020. So despite more money in my wallet (nominal increase) I actually lost some real value. Eh.
OK, let’s try to be optimists here. The glass is half full. By putting the money on the deposit (5% yearly) we lost some value. Still, if we left it on an ordinary account with a lousy 0.5% interest rate the financial outcome would be even more unsatisfactory.
(
# nominal gain
getFormattedMoney(
getValue(money2019, 0.5, numYrs)),
# real value
getFormattedMoney(
getValue(money2019, repeat([0.5], numYrs), inflPoland))
)
("10,253 USD", "7,146 USD")
So always look on the bright side of life, but look for something better and think before you leap.