Tuesday, June 30, 2020

A Programmer's Understanding Of e

Imagine I get a request from a bank to program the function that calculates money earned from one of their accounts. This seems too easy, but here's the code:

(define ($ amt)
  (inexact amt))


(define (grow/1 balance rate)
  (let ((interest (* balance (/ rate 100))))
    ($ (+ balance interest))))

;; Starting balance is $37 and growing by 8%
> (grow/1 37 8)
39.96

;; Confirm an edge case. 0% means no growth.
> (grow/1 37 0)
37.0

;; Another edge case. Growing 100% means doubling your money.
> (grow/1 37 100)
74.0
> 

"Close," the bank replies, "but not quite." The issue: we need to support growing the money in multiple increments. My imaginary contact explains:

Along with promising our customers 8% interest, we also tell them that their money will grow quarterly. So rather than one 8% increase at the end of the year, we give four 2% increases throughout the year.

That's not too tricky to program. Now my growth function takes in an additional frequency argument:

(define (grow/2 balance rate frequency)
  (let ((r (if (or (= 0 rate) (= 0 frequency))  0
               (/ (/ rate frequency) 100))))
    (let loop ((balance balance) 
               (frequency frequency))
      (cond ((= frequency 0) ($ balance))
            (else
             (let* ((interest (* balance r)))
               (loop ($ (+ balance interest))
                     (- frequency 1))))))))
;; The same $37 at 8%, but now grown quarterly
(grow/2 37 8 4)
40.04998992000001

;; The 0% interest edge case still produces no growth. Good.
> (grow/2 37 0 4)
37.0

;; Growing by 100% now *more* than doubles your money. Nice!
> (grow/2 37 100 4)
90.33203125

Growing the money 4 times a year at 8% gives a slightly better return. But check out that 100% edge case: that now more than doubles the money in the account. Looking at these examples one wonders if we can get a better return just by having the bank grow the money more often. Let's try it:

;; Quarterly - good.
> (grow/2 37 100 4)
90.33203125

;; Monthly - better.
> (grow/2 37 100 12)
96.68230573831309

;; Daily - best!
> (grow/2 37 100 365)
100.43899683480936

;; 1000 times per year
> (grow/2 37 100 1000)
100.52618549272809

;; 5000 times per year
> (grow/2 37 100 5000)
100.56637185376789

This strategy of incrementing the balance more often starts off promising. If we can get the bank to increment our money every day for a year we'll make a little over $100, versus the $90 we'll make growing the money quarterly. However, there's a limit to this strategy. Growing the money 1000 or 5000 times doesn't get us past the $100 return.

This makes sense: we may be able to convince the bank to grow our money 5000 times a year instead of 4--which sounds good--but each increase is using an itty bitty (a technical math term) interest rate.

So this is intereting, but what does this have to do with the math constant e.

Check this out: if you look up e you'll see that its value is approximiately:

  e = 2.718281828459045

Now let's say we run our growth/2 function on the simplest possible arguments: $1 grown at 100%. We get:

> (grow/2 1 100 100000)
2.718268237174493

That number is pretty close to e. Coincidence? Nope. An Intuitive Guide To Exponential Functions & e explains:

The number e (2.718…) is the maximum possible result when compounding 100% growth for one time period. Sure, you started out expecting to grow from 1 to 2 (that’s a 100% increase, right?). But with each tiny step forward you create a little dividend that starts growing on its own. When all is said and done, you end up with e (2.718…) at the end of 1 time period, not 2. e is the maximum, what happens when we compound 100% as much as possible.

So e isn't some mysterious number. It's the result of (grow/2 1 100 x) where x is very large number. So large that it's just about infinity. As we suspected, e is a limit:

e is like a speed limit (like c, the speed of light) saying how fast you can possibly grow using a continuous process. You might not always reach the speed limit, but it’s a reference point: you can write every rate of growth in terms of this universal constant.

While it's novel that we can discover this limit ourselves, that's not the whole story.

Back in 2018 I experimented with an animation framework with an odd quirk. You could draw exactly one thing: lines from (0,0) to (0,1). You could then scale, rotate and translate these lines. So while you could only draw one type of line, you could transform their size, shape and position in whatever you imagined. You can play a similar game with e. By raising e to a power, you can scale it to calculate any growth rate. Including negative growth (I'm looking at you radioactive half-life).

As a sign of how frequently one raises e to various powers Scheme names this function simply exp. Here's yet another grow function, this time powered by e.

(define (grow/3 balance rate)
  ($ (* balance (exp (/ rate 100)))))

> (grow/3 37 8)
40.08162150397347
> (grow/3 37 0)
37.0
> (grow/3 37 100)
100.57642765298466

So not only do we have a sense of what e is, we now have a new tool in our tool kit: ex. If you find yourself dealing with problems relating to continuous exponential growth, like say an out of control pandemic, e is your friend.

No comments:

Post a Comment