January 13th, 2024 The Math you Really Need as a Programmer By Jeffrey M. Barber

Adama has complex numbers, and I love complex numbers. I was talking with a product engineer about a range check where I nit-picked an expression x >= low && x <= high should be written as low <= x && x <= high. It’s a good nit-pick (IMO), but then I lamented that it should really be low <= x <= high. I lamented that I haven’t figure that out yet for my parser and type system… “I miss Math” was the driving internal sentiment, and in my nostalgic state I volunteered the fact that Adama has complex numbers via the keyword @i. Then, I shared a wiki link around complex numbers, and I got the question “which kinds of math do you think are most useful for what you do? calculus?”

This is a very good question, and I like it because I believe engineers should be focused on growth. Ultimately, I do believe most people are utilitarians at core constantly prioritizing what they should know given how hard it is to actually know anything, and I feel a deep despair around the state of Mathematical education. Getting good at Math is really despite education, so I want to share how I think about Math from two stories in my earlier days, make a proposal for future generations, and then make a recommendation for anyone that wishes to deepen their understanding of Mathematics that I view as absolutely critical.

Two Stories

I invented algebra in sixth grade

I admit that I was an odd child fixated on these infernal machines, and I would read big thick books on DOS while in elementary school. I tinkered with QBasic, and I was trying to learn how to make games. My favorite games at the time were L.O.R.D. and Dragon Warrior on NES.

Since my ability to do graphics was limited, I tried to merge these two games in an awkward way. At a point, I was creating a table of monsters where each monster gave XP for defeat, and then I was creating a level table for players to ascend. I was playing with the concept of fun in the context of killing monsters and how to make monsters hard, and it was a lot of trial and error.

When it came to the level system, I realized that the number of monsters required to defeat should depend on my current level and the monsters level. I like games that give the player the choice to grind on a legion of beasts versus fighting a reasonable number of level appropriate monsters at elevated strategic difficulty. So, I had this product insight that going from level N to level N + 1 should take roughly 10 level N monsters and 100 level N - 1 monsters. With only a notion of arithmetic, I then turned to code to help me produce an XP table.

Since I don’t remember BASIC at all, I’ll write the logic I remember in JavaScript.

function next_monster_xp(monster_xp) {
  var total_xp_to_gain = 100 * monster_xp;
  var next_monster_xp = monster_xp;
  while (true) {
    next_monster_xp++;
    if (next_monster_xp * 10 >= total_xp_to_gain) {
        return next_monster_xp;
    }
  }
}

var current_monster = 7;
var level_table = [];
var current_xp = 0;

for (var level = 1; level <= 100; level++) {
  current_xp += current_monster * 10;
  level_table.push(current_xp);
  current_monster = next_monster_xp(current_monster);
}

I didn’t realize at the time that I was solving a very simple linear equation. In this case, the product insight could be written as

100 * MPlvl = 10 * MPlvl+1

But I was able to adjust next_monster_xp

function next_monster_xp(monster_xp) {
  return 100 * monster_xp / 10.0;
}

And I got the same result significantly faster. Once I got to seventh grade, the Math clicked really well because I already had a basis for why I should learn this stuff.

On deriving the quadratic formula on a road-trip

I’m not good at rote memorization, but I do remember things I “invent” very well. I didn’t have my textbook at the time, and I was trying to understand the quadratic formula. It frustrated me that I couldn’t remember it, so I tried deriving it. What else was I going to do?

So, I was a curious monkey and looked at the picture and noticed symmetry, and I wondered if I could exploit that. So, I noted that there is a line x=S such that f(x + r) = f(x - r) where f(x) = Ax^2 + Bx + C, and then I just started messing around with the Math and playing with it.

A(x + r)^2 + B(x + r) + C = A(x - r)^2 + B(x - r) + C

the Cs immediately cancel which is nice.

A(x + r)^2 + B(x + r) = A(x - r)^2 + B(x - r)

Then let’s just expand this into a hot mess.

Ax^2 + A2xr + Ar^2 + Bx + Br = Ax^2 - A2xr + Ar^2 + Bx - Br

Wowza, it’s a cancelation festival: Ax^2, Ar^2, and B*x all cancel out

A2xr + Br = -A2xr - Br

Woah, now the r’s cancel out as they are not zero.

A2x + B = -A2x - B

Now, let’s bring the x to the let and the B to the right

A4x = -2*B

Divide both sides by 4*A

x = -2B/(4A)

And simplify

x = -B/(2*A)

At this point, I haven’t fully reconstructed the quadratic formula, but I got a big clue! I tried plugging both y = x + B/(2A) and y = x - B/(2A) to see what would happen.

Starting with y = x + B/(2*A)

f(y) = f(x + B/(2A)) = A(x + B/(2A))^2 + B(x + B/(2*A)) + C = 0

which yielded

Ax^2 + A2xB/(2A) + A(B/(2A))^2 + Bx + BB/(2A) + C = 0

which simplifies to

Ax^2 + 2xB + A(B/(2A))^2 + BB/(2*A) + C = 0

and, this was a disappointment, so I tried y = x - B/(2*A)

f(y) = f(x - B/(2A)) = A(x - B/(2A))^2 + B(x - B/(2*A)) + C = 0

which expands into

Ax^2 - A2xB/(2A) + A(B/(2A))^2 + Bx - BB/(2A) + C = 0

and we cancel 2*A

Ax^2 - xB + A(B/(2A))^2 + Bx - BB/(2*A) + C = 0

Oh, look, the B*x cancel out

Ax^2 + A(B/(2A))^2 - BB/(2*A) + C = 0

Oh, neat! it’s a bunch of constants with a single x^2 term

Ax^2 = -C + BB/(2A) - A(B/(2*A))^2

simplifying the A(B/(2A))^2 we get…

Ax^2 = -C + BB/(2A) - BB/(4*A)

ok… that’s very odd, but it’s something

x = +/- sqrt(-C + BB/(2A) - BB/(4A)) / sqrt(A)

For a while, this didn’t sit right with me, so I went back to

Ax^2 = -C + BB/(2A) - BB/(4*A)

and multiplied both sides by 4*A

4AAx^2 = -4AC + 4ABB/(2A) - 4ABB/(4*A)

and the simplified

4AAx^2 = -4AC + 2BB - BB/(4*A)

and grouped like terms

4AAx^2 = -4A*C + B^2

brought the 4*A under the square

(2Ax)^2 = -4AC + B^2

which now allows us to bring the sqrt into the picture

2Ax = +/- sqrt(-4AC + B^2)

and…

x = +/- sqrt(-4AC + B^2)/(2*A)

which then combines with y = x - B/(2*A)

to give

y = +/- sqrt(-4AC + B^2)/(2A) - B/(2A)

which can be fiddled with to get

y = (-B +/- sqrt(-4AC + B^2)) / (2*A)

which is the quadratic formula and I had a very awkward way of deriving it.

Now, I was in high school school figuring this out in a car ride, and the reason I share this because it wasn’t elegant. It was a LOT of paper work, but it was the result of curiosity. Sure, I could memorize the formula, but now I had a method of deriving it at any time of my own invention.

It also gave me a deeper insight as to something like Ax^2 plus Bx looks like a x^2, and that’s because the B*x is a translation operation! Which… is neat?

Now, the reason I share this story is that becoming good requires being engaged (or, enraged) and curious. Most people I know that have learned Math well did so in spite of their education, and if you’re curious and willing to play then that is the way.

Fixing Math education

At core, fixing Math education requires putting the horse ahead of the cart. The why must come before the what and how, and currently we teach how with weak what justifications and way too many hypotheticals. Now, I want to be clear that K-6 is fine because it is super basic, but beyond arithmetic… it’s the blind teaching the blind.

I would place computer programming and computer science at the heart of Mathematical curriculum since the core of programming is about processes and algorithms. Mathematics then is about optimization, detecting patterns, technique, and elegance. Making the machine behave and perform becomes the horse, and everything else is the cart in service of that goal.

After-all, before computers, math was all we had to protect us from mind-numbing error-prone predictions.

In terms of course material, game programming can take people from arithmetic to differential equations. From there, motivated students can take on a more traditional higher Mathematical education focused on proving results. This is basically what happens today because you re-take Calculus I-III by proving everything in Advanced Calculus I & II, and there will always be the curious student that wants to poke underneath the rules.

What practitioners should do today.

Suppose a person lacks a strong background in Math and computer science (and this includes most engineers I know), then I have a fairly simple suggestion. Be like the Jedi, and build your own light-saber language starting with abstract syntax trees. Write a program to parse:

myLang("(+ 1 2 3)");
myLang("(+ 1 (* 42 13))");

Once you have a well tested way of parsing trees and the ability to compute with just addition, subtraction, multiplication, and division… Introduce identifiers, and then realize the result isn’t just a number. It’s another tree. Use a real language (like Java), and then go nuts and introduce whatever you want. As an example, here are some things to assist adapting your programming knowledge into your new calculator.

// boolean values
myLang("(true)");
myLang("(false)");

// logic operators
myLang("(|| true false)");
myLang("(|| true false true false)");
myLang("(&& true false true)");
myLang("(^^ true false)");

// variables
myLang("(define x 123)");

// sums/products
myLang("(sum x 0 10 x))");
myLang("(product x 1 10 x))");

// functions
myLang("(define factorial (function n (product k 1 n k)))");

At the same time, you start introducing algebra and calculus into the mix as well

myLang("(expand (* (+ x 1) (+ x 2))");
myLang("(factor (+ (^ x 2) (* 2 x) 2))");

myLang("(differentiate (^ x 2))");
myLang("(integrate (^ x 2))");

With this, you can make your own environment for playing and learning Math at a structural level. Since the core input is a string, you can make REPL for your language and introduce an environment to have all sorts of fun! This task is infinite, but it also peaks behind the curtain of the daily job of writing code. Whatever language you use has some concept of an abstract syntax tree and many rules to define a game.

The goal of this project is to deepen your how and what of the core task of writing code and solving problems. However, there is a trick here in “how do you validate” the input is good and correct.

Git Guuuud, Scrub

The boundary between “just a programmer” and a “good engineer” is discipline of the mind to enforce good error handling or remove errors by simplifying the process. Error handling requires a rigorous understanding of the state space, and simplification removes possible state spaces. Hell, even simplifying polynomials has a purpose; for example, converting (x+1)(x+2)/(x+1) to (x+2) when x != -1 is not only faster to compute but has less numerical error since machines don’t have infinite precision. My view is that engineering goes beyond “getting the job done” and places an inherent value on doing things well with less.

Solving isolated problems that introduce more problems (like an untyped language) is a great way to practice exceptional error handling and understanding the growth of state space, and I’m a strong believer in unit tests. It’s just like doing push-ups, figure out a way to do tests to push more code (in terms of performance review, PUMP THOSE NUMBERS) and solve more problems. As an example, Adama has 6250 unit tests that pass reliably.

So, when I think about how to answer “which kinds of Math do you think are most useful for what you do? calculus?”

Well, beyond arithmetic and basic algebra, it’s really about the discipline of mind. You can’t really do Math well without discipline as you’ll get wrong answers. The careful fastidious process of doing Math is a proxy skill (reward?) because you can take things at the appropriate pace and have well defined justifications. That’s the true value of a Mathematics education.

Like anything, the toil of doing discipline must be in alignment with core incentives which is where I believe modern Mathematics fails. And, at the same time, you need to know that Mathematics is just another game that you can play with. However, if you’re curious then there are two books I recommend:

First, Mathematics: From the Birth of Numbers is a great whirlwind tour of the entire “rote” mathematics classes. Second, I highly recommend Numbers which illustrates just how constructed the numbers we use are.

For example, you can start with just the natural numbers (1, 2, 3) and then construct the integers by definition a pair of whole numbers (a, b) with a rule system. For example, positive X is just (X + y, y) for all y in the natural numbers and zero is just (y, y) for all y in the natural number. You define addition and multiplication as rules, then define subtraction and you’ve just invented negative numbers using only addition and whole numbers (isn’t that #wild?).

There is a deep relationship between “high level Mathematics” and “programming” as you construct new things, define rules, prove properties of the ways those rules interact. Mathematics is fundamentally systems engineering! Or, is systems engineering a branch of Mathematics!

For anyone that wishes to dive deeper, I salute you. May you find victory in your endeavors, and may the suffering turn to sweetness.


bonus observation: I capitalize M in Mathematics for the same reason people capitalize the G in God.