Prev | Contents | Next

15 Appendix A: Basic Math for Programmers

I know what you’re thinking: screw this.

Or maybe your language is even more blue.

But bear with me for just a moment. Knowing a few basic mathematical operations can really help you be a better developer, as well as help you understand code that you come across.

This section isn’t so much about application of these functions, but is more about their definition. It’ll be up to you to use them as you see fit.

It’s a short one—just plow through it.

Fun Math Fact: Math is cool.

15.1 Arithmetic

Addition, subtraction, multiplication, and—don’t fall asleep on me already—division. The Big Four of grade school math.

And, as a quick terminology summary:

I’m not going to talk about what the operators do, and will presume you remember that from however-many years ago.

But I do want to talk about which comes first, because you might have forgotten.

What do I mean by that?

Take this expression: \(1+2\times3\)

If we first add \(1+2\), we get \(3\). And then we multiply it by \(3\) and we get the answer of \(9\).

But wait! If we first multiply \(2\times3\) and get \(6\), then we add \(1\) we get an answer of \(7\)!

So… which is it? \(9\) or \(7\)?

What we need to know is the order of operations or the precedence of one operator over another. Which happens first, \(+\) or \(\times\)?

For arithmetic, here is the rule: do all the multiplications and divisions before any additions and subtractions.

So: \(1+2\times3=7\)

We’ll revisit order of operations as we continue on.

In Python, we can just use the operators +, -, *, and / for addition, subtraction, multiplication, and division, respectively.

15.2 Division in Python

Hey—didn’t we just talk about this?

Yes, kinda. There are a few details that are of interest.

Let’s divide!

print(3 / 2)   # 1.5, as expected

What we’ve done there is a floating point division. That is, it produces a floating point result with a decimal point. Indeed, even this does the same:

print(2 / 1)  # 2.0

So that’s the “normal” way of division, right? No big surprises there.

But what if I told you there is no spoon164?

Okay, that’s a bit much. Never mind that analogy.

But there is another kind of division, one that produces an integer result only. Integer division uses the // operator (two slashes instead of just one). It returns an integer result in all cases, even if normally there would be a fraction.

It does this by simply dropping the part after the decimal point like a hot potato.

print(9 / 5)   # 1.8
print(9 // 5)  # 1

When you want an integer quotient, this is the fast way to do it.

15.3 Modulo, AKA Remainder

So when you do an integer division, it just cuts off the fractional part after the decimal point. What happens to it? Where does it go? If a tree falls in the forest and no one is around to hear it…?

Well, it’s gone forever, but there’s a way to see what it would have been in the form of a remainder.

The remainder tells us how much is “left over” after the integer division has taken place.

In the following examples, we’ll use \(\div\) to represent integer division.

For example, if we take 40 and divide it into 4 parts, \(40\div4\), we get a result of \(10\), exactly. Because it’s exact, there are no leftovers. So the remainder is zero.

But if we take 42 and divide it into 4 parts… well, \(42\div4=10\) still. But it’s not exact. If we do the inverse and multiply \(10\times4\), we only get \(40\), not \(42\). There are \(2\) left over. The remainder is \(2\)!

We do this with the modulo operator, or mod for short.

\(42\div4=10\)

\(42\bmod4=2\) The remainder!

And in Python, the % operator is the modulo operator.

print(42 // 4)  # 10
print(42 % 4)   # 2

Mod has the neat property of rolling numbers over “odometer style” because the result of the mod can never be larger than the divisor.

As an example:

for i in range(10):
    print(i % 4)   # i modulo 4

outputs:

0
1
2
3
0
1
2
3
0
1

15.4 Negative numbers

I’m not going to talk much about these here, but negative numbers are numbers less than 0, and they’re indicated by a minus sign in front of the number. \(-7\) means “\(7\) less than zero”.

And I know that’s a crazy concept. Like how can you have \(-12\) apples on the table? It doesn’t seem particularly rooted in reality.

But a closer to home example might be me saying, “I owe you $-35!” which means, in effect, you owe me $35!

Remember some simple rules:

If you multiply a negative number by a negative number, the result is positive.

\(-3\times-7=21\)

If you multiply a negative number by a positive number, the result is negative.

\(-3\times7=-21\)

If you add a negative number to a positive number, it’s just like subtracting the negative number from the positive number:

\(-7+100=93\)

or, equivalently:

\(100+(-7)=100-7=93\)

15.5 Absolute Value

Think of this as how far from zero on the number line any number is.

10 is 10 away from zero. The absolute value of 10 is 10.

2.8 is 2.8 away from zero. The absolute value of 2.8 is 2.8.

Well, that seems a bit bland. Why bother?

Here’s why:

-17 is 17 away from zero. The absolute value of -17 is 17.

The rules are:

In summary, if it’s negative, the absolute value makes it positive.

In mathematical notation, we represent the absolute value with vertical bars:

\(x = -17\) If \(x\) is \(-17\)

\(|x| = 17\) …the absolute value of \(x\) is \(17\).

\(|12| = 12\) The absolute value of \(12\) is \(12\).

\(|-13.8| = 13.8\) The absolute value of \(-13.8\) is \(13.8\).

In Python we compute absolute value with the abs() built-in function:

print(abs(-17))  # 17
print(abs(28.2)) # 28.2

In terms of precedence, you can think of absolute value kind of like parentheses. Do the stuff inside the absolute value first, then take the absolute value of the result.

15.6 The Power of Exponents

Let’s say you wanted to multiply the number \(7\) by itself, say, 8 times. We could write this:

\(7\times7\times7\times7\times7\times7\times7\times7\)

I guess that’s not entirely awful.

So let’s go all out. I want to multiply \(7\) by itself \(3490\) times. I’m going to head to the pub while you work on that. Let me know when you’re done.

Luckily for you, mathematicians are lazy. They hate writing more than they have to, so they invent new notations out of thin air to avoid the extra work. Just like how I keep packing the kitchen trash down so I don’t have to run it outside.

Actually, no, it’s not really like that at all165.

So what they invented is another way of saying “3490 7s multiplied by each other” that didn’t involve an endless line of \(7\)s and \(\times\)s. And it looks like this:

\(7^{3490}\) (read “7 to the 3490th power”)

That means 3490 \(7\)s multiplied together. We call this raising to a power or exponentiation.

Or, put another way:

\(7^8=7\times7\times7\times7\times7\times7\times7\times7\)

We have all kinds of fun facts! Ready?

In the example \(7^8\), the number \(7\) is what we call the base and the number \(8\) we call the exponent.

If you want to write that code in Python, you’d write:

print(7**8)  # prints 5764801

The exponent must be non-negative, so zero or larger.

But wait—zero? How do you multiply a number by itself zero times? It’s a valid question, and one that has baffled philosophers for time immemorial. But not mathematicians! Like power-crazed numerical dictators, they cried, “We made this up, and we declare that anything to the zeroth power is \(1\). End of story!”

So there we have it. \(n^0=1\) for all \(n\).

But their unquenchable thirst for power didn’t end there. Mathematicians also decided that there was such a thing as negative exponents.

If you have a negative exponent, you need to invert the fraction before applying the exponent. The following is true:

\(4^{-3}=\left(\cfrac{1}{4}\right)^3\)

\(\left(\cfrac{3}{4}\right)^{-8}=\left(\cfrac{4}{3}\right)^8\)

And in case you’re wondering how to raise a fraction to an exponent, you just apply the exponent to the numerator and denominator:

\(\left(\cfrac{4}{3}\right)^8=\cfrac{4^8}{3^8}=\cfrac{65536}{6561}\)

We also have some shorthand names for certain exponents.

\(n^2\) we say “\(n\) squared”. Anything raised to the power of \(2\) is that number squared.

\(n^3\) we say “\(n\) cubed”. Anything raised to the third power is that number cubed.

In casual writing, you’ll often see the caret character ^ used to indicate an exponent. So if someone writes 14^4, that’s the same as \(14^4\).

Lastly, precedence. We know that multiplication and division happen before addition and subtraction, but what about exponentiation?

Here’s the rule: exponentiation happens before arithmetic.

With \(2+3^4\), we first compute \(3^4=81\), then add \(2\) for a total of \(83\).

15.7 Parentheses

So what if you want to change the order of operations, like some kind of mathematical rebel?

What if you have \(1+2^3\) and you want \(1+2\) to happen before the exponentiation?

You can use parentheses to indicate that operation should occur first, like this:

\((1+2)^3\)

With that, we first compute \(1+2=3\), and then we raise that to the 3rd power for a result of \(27\).

You could also do this:

\(2^{(3+4)}\)

Remember: parentheses first! \(3+4=7\), so we want to compute \(2^7\) which is \(128\). (Good software developers have all the powers of \(2\) memorized up through \(2^{16}\). Well, crazy ones do, anyway.)

Python uses parentheses, as well. The above expression could be written in Python like this:

2**(3+4)

Easy peasy.

One final note: if an exponent is a long expression without parentheses, it turns out the parentheses are implied. The following equation is true:

\(2^{(3+4)}=2^{3+4}\)

15.8 Square root

Square root is a funny one. It’s the opposite of squaring, which if you remember from the earlier section means raising to a power of 2. But, again, square root is the opposite of that.

It’s asking the question, “For a given number, which number do I have to multiply by itself to get that number?”

Let’s examplify!

But before we do, the weird lightning bolt line thing is the mathematical symbol for the square root of a number.

Let’s ask for the square root of 9:

\(\sqrt9=?\)

That’s asking, what number multiplied by itself makes \(9\)? Or, in other words, what number raised to the 2nd power makes \(9\)? Or, in other words, what number squared makes \(9\)?

Hopefully by now you’ve determined that:

\(3\times3=9\)

and, equivalently:

\(3^2=9\)

so therefore:

\(\sqrt9=3\)

What about some other ones?

\(\sqrt{16}=?\) \(\sqrt{25}=?\) \(\sqrt{36}=?\) \(\sqrt{12180100}=?\)

Note that all of those have integer answers. If the square root of number is an integer, we call that number a perfect square. 16, 25, 36… these are all perfect squares.

But you can take the square root of any number.

\(\sqrt{17}=4.123105625617661\) approximately.

17 is not a perfect square, since it doesn’t have an integer result.

Now, you might be imagining what would happen if you tried to take the square root of a negative number:

\(\sqrt{-100}=?\)

After a bit of thought, you might realize that no number multiplied by itself can ever result in \(-100\) so… what can you do?

You might think, “We’re doomed,” but not mathematicians! They simply defined:

\(\sqrt{-1}=i\)

and invented an entire mathematical system around what they called imaginary numbers166 that actually have some amazing applications. But that’s something you can look up on your own.

In addition to square roots, there are also cube roots. This is asking, “What number cubed results in this number?” This is indicated by a small \(3\) above the root symbol:

\(\sqrt[3]{27}=x\)

which is the inverse of the equation:

\(x^3=27\)

Can you figure out what \(x\) is?

What about how to do this in Python?

For square roots, the preferred way is to use the sqrt() function in the math module that you import:

import math

print(math.sqrt(12180100))  # prints 3490.0

What about cube roots? Well, for that, we’re going to jump back to exponents and learn a little secret. You can raise numbers to fractional powers. Now, there are a lot of rules around this, but the short of it is that these equations are true:

\(\sqrt{x}=x^\frac{1}{2}\)    \(\sqrt[3]{x}=x^\frac{1}{3}\)    \(\sqrt[4]{x}=x^\frac{1}{4}\)

and so on. Raising a number to the \(\frac{1}{3}\) power is the same as taking the cube root!

Like if we wanted to compute the cube root of \(4913\), we could compute:

\(\sqrt[3]{4913}=4913^\frac{1}{3}=17\)

And you can do that in Python with the regular exponent operator **:

print(4913**(1/3))  # prints 16.999999999999996

Hey—wait a second, that’s not \(17\)! What gives?

Well, turns out that floating point numbers in computers aren’t exact. They’re close, but not exact. It’s something developers need to be aware of and either live with or work around.

Lastly, because square root, cube root, and all the other roots are just exponents in disguise, they have the same precedence as exponents: they happen before arithmetic.

15.9 Factorial

Factorial is a fun function.

Basically if I ask for something like “5 factorial”, that means that I want to know the answer when we multiply 5 by all integers less than 5, down to 1.

So I’d want to know:

\(5\times4\times3\times2\times1\)

the answer to which is \(120\).

But writing “factorial” is kind of clunky, so we have special notation for it: the exclamation point: \(!\). “5 factorial” is written \(5!\).

Another example:

\(6!=6\times5\times4\times3\times2\times1=720\)

As you can imagine, factorial grows very quickly.

\(40!=815915283247897734345611269596115894272000000000\)

In Python, you can compute factorial by using the factorial() function in the math module.

import math

print(math.factorial(10))  # prints 3628800

Factorial, being multiplication in disguise, has the same precedence as multiplication. You do it before addition and subtraction.

15.10 Scientific notation

For really large floating point numbers, you might see things like this appear:

print(2.7**100)  # 1.3689147905858927e+43

What’s that e+43 at the end?

This is what we call scientific notation. It’s a shorthand way of writing really large (or small) numbers.

The number above, 1.3689147905858927e+43, is saying this, mathematically:

\(1.3689147905858927\times10^{43}\)

Now, \(10^{43}\) is a big number. So multiplying \(1.3689\) etc. by it results in a very large number, indeed.

But that’s not all. We can use it to represent very small numbers, too.

print(3.6/5000000000000000000)   # 7.2e-19 

7.2e-19 means:

\(7.2\times10^{-19}\)

And \(10^{-19}\) is a very small number (very close to 0—remember that \(10^{-19}=\frac{1}{10^{19}}\)), so multiplying \(7.2\) by it results in a very small number as well.

Remember this:

15.11 Logarithms

Hang on, because things are about to get weird.

Well, not too weird, but pretty weird.

Logarithms, or logs for short, are kind of the opposite of exponents.

But not opposite in the same way square roots are opposite.

That’s convenient, right?

With logs, we say “log base x of y”. For example, “log base 2 of 32”, which is written like this:

\(\log_2{32}\)

What that is asking is “2 to the what power is 32?” Or, in math:

These are both true:

\(x=\log_2{32}\)

\(2^x=32\)

So what’s the answer? Some trial and error might lead you to realize that \(2^5=32\), so therefore:

\(x=\log_2{32}=5\)

There are three common bases that you see for logs, though the base can be any number: 2, 10, and \(e\).

Base 2 is super common in programming. In fact, it’s so common that if you ever see someone write \(\log{n}\) in a computing context, you should assume they mean \(\log_2{n}\).

\(e\) is the base of the natural logarithm167, common in math, and uncommon in computing.

So what are they good for?

A common place you see logarithms in programming is when using Big-O notation to indicate computational complexity168.

To compute the log of a number in Python, use the log() function in the math module. You specify the base as the second argument. (If unspecified, it computes the natural log, base \(e\).)

For example, to compute \(\log_2{999}\):

import math

print(math.log(999, 2))   # Prints 9.96434086779242

# Because 2^9.96434086779242 == 999. Ish.

The big thing to remember there is that as a number gets large, the log of the number remains small. Here are some examples:

x log2x
1 0.0000
10 3.3219
100 6.6439
1000 9.9658
10000 13.2877
100000 16.6096
1000000 19.9316
10000000 23.2535

The log-base-2 of a big number tends to be a much smaller number.

15.12 Rounding

When we round a number, we are looking for the nearest number of a certain number of decimal places, dropping all the decimal places smaller than that.

By default, we mean zero decimal places, i.e. we want to round to the nearest whole number.

So if I said, “Round 2.3 to the nearest whole number,” you’d answer “2”, because that’s the closest whole number to 2.3.

And if I said, “Round 2.8 to the nearest whole number,” you’d answer “3”, because that’s the closest whole number to 2.8.

When we round to a higher number, we call that rounding up.

The other direction is rounding down.

But what if I said “Round 2.5 to the nearest whole number?” It’s perfectly between 2 and 3! In those cases, conventionally, lots of us learned to round up. So the answer would be 3.

We can also force a number to round a certain direction by declaring which way to round.

“Divide x by 3, then round up.”

In Python, we have a few options for rounding.

We can use the built-in round() function.

But it behaves a little bit differently than we might be used to. Notably, numbers like 1.5, 2.5, and 3.5 that are equally close to two whole numbers always round to the nearest even number. This is commonly known as round half to even or banker’s rounding.

round(2.8)  # 3
round(-2.2) # -2
round(-2.8) # -3

round(1.5)  # 2
round(2.5)  # 2
round(3.5)  # 4

You can also tell round() how many decimal places you want to round to:

round(3.1415926, 4)  # 3.1416

Note that Python has additional weird rounding behavior169 due to the limited precision of floating point numbers.

For always rounding up or down, use the functions ceil() and floor() from the math module.

ceil() returns the next integer greater than this number, and floor() returns the previous integer smaller than this number.

This makes perfect sense for positive numbers:

import math

math.ceil(2.1)  # 3
math.ceil(2.9)  # 3
math.ceil(3.0)  # 3
math.ceil(3.1)  # 4

math.floor(2.1)  # 2
math.floor(2.9)  # 2
math.floor(3.0)  # 3
math.floor(3.1)  # 3

But with negative numbers, it behaves differently than you might expect:

import math

round(2.3)        # 2
math.floor(2.3)   # 2

round(-2.3)       # -2
math.floor(-2.3)  # -3 (!!)

round(2.8)        # 3
math.ceil(2.8)    # 3

round(-2.8)       # -3
math.ceil(-2.8)   # -2 (!!)

While round() heads to the nearest integer, floor() goes to the next smallest integer. With negative numbers, that’s the next one farther away from zero. And the reverse is true for ceil().

If you want a round up function that works on positive and negative numbers, you could write a helper function like this:

import math

def round_up(x):
    return math.ceil(x) if x >=0 else math.floor(x)

round_up(2.0)   # 2
round_up(2.1)   # 3
round_up(2.8)   # 3

round_up(-2.0)   # -2
round_up(-2.1)   # -3
round_up(-2.8)   # -3

I’ll leave round_down() as an exercise. :)

If you want to always round up to the next whole number instead of doing banker’s rounding, you can use this trick: add 0.5 to the number and then convert it to an int(). (If the number is negative, subtract 0.5 from it.)

int(0.5 + 0.5)  # 1
int(1.5 + 0.5)  # 2
int(2.5 + 0.5)  # 3

int(2.8 + 0.5)  # 3
int(2.2 + 0.5)  # 2

int(-2.2 - 0.5) # -2
int(-2.8 - 0.5) # -3

15.13 Large Numbers

Python is really good with large integer numbers. In fact, it has what we call arbitrary precision integer math. That means we can hold numbers as big as memory can handle, which is large.

So if you ask Python to compute \(100000!\), it will do it. The result, incidentally, is 465,575 digits long. No problem.

But the same is not true for floating point numbers (numbers with a decimal point). Python generally uses 64 bits to represent floating point numbers, which means there’s a limit to the amount of precision—you can only have 16 digits in your number, though you can raise that number to huge positive or negative powers to get very large or very small numbers.

Floating point is brilliant because it can represent really huge and really small numbers, but the number of digits you have at your disposal is limited.


Prev | Contents | Next