Lecture 5

How to read these lecture notes: In the menu in Jupyter, select Kernel -> Restart & Clear Output. Then read through each line, and as you go forward, try to guess what the code written there will do. Sometimes, if the task is clear, erase the code there and try to write it yourself. (You can always copy paste back from the html version of the notebook)

A few loose ends, and then more loop examples.

First: I forgot to tell you about in-line if-else. Not that you really need it, but it's neat because it's one line:

In [90]:
x = 0 if True else 1
print(x)
0
In [91]:
x = 0 if True else 1
print(x)
0


Warning: Never test for equality of floats

In [92]:
0.1 + 0.2 == 0.3
Out[92]:
False

???? It's because of rounding errors in float operations

In [93]:
print(0.1 + 0.2)
0.30000000000000004

Can't trust anybody these days!


Similarly: $$cos({60^o}) = sin(30^o) $$ but:

In [94]:
import math
math.cos(math.pi/3) == math.sin(math.pi/6)
print(math.cos(math.pi/3))
print(math.sin(math.pi/6))
0.5000000000000001
0.49999999999999994

Solution: instead check to see if difference is very small:

In [95]:
epsilon = 0.0000000001
abs(0.3 - (0.1 + 0.2)) < epsilon
Out[95]:
True

Let's even make it into a function:

In [96]:
def are_almost_equal(x,y):
    return True if abs(x-y) < 0.00000001 else False
In [97]:
are_almost_equal(0.1+0.2, 0.3)
Out[97]:
True
In [98]:
are_almost_equal(math.cos(math.pi/3), math.sin(math.pi/6))
Out[98]:
True
In [99]:
are_almost_equal(0.1+0.2, 0.300002)
Out[99]:
False


More examples with loops, break, continue

Recall while loops:

In [130]:
x = 0
while x <= 10:
    print(x*x)
    x = x + 1
0
1
4
9
16
25
36
49
64
81
100


Let's do another mathematical thing with while loops

Remainder of division

Division of $a$ by $b$: $$a = qb + r$$ Where $r$ is the unique remainder $$ 0 \leq r < b$$

Let's implement the remainder. (normally we would just use the built-in mod operator: 17 % 5 = 2 but we want to see how to do it ourselves)

When thinking about problems, you should always work out the algorithm on paper before writing any code.

The idea: let's say a=15, b=4. To find the remainder, I can remove b from a as many times as I can as long as I don't go below zero.

Pseudo-code (not always necessary but helps you think):

start with a,b
as long as a>=b
    a <- a - b  (set a to be a-b)
answer is a


Here's the code:

In [142]:
def remainder(a,b):
    while a >= b:
        a = a - b
    return a
In [143]:
remainder(15,4)
Out[143]:
3

But won't changing a during execution change the values of the variables we put in?

In [144]:
x = 10
y = 3
remainder(x,y)
Out[144]:
1
In [145]:
print(x,y)
10 3

No, even though a in the function was x, it was actually a copy of x and changing the value didn't change x's value. Just like local variables inside the function, the values of the arguments of the function are copied and not changed. (But, we will need to be careful about this in the future when we work with more complicated objects we put in as function arguments))


Actually there are several mistakes in out algorithm! Can you find them?

first, we could plug in b=0, which would cause an infinite loop.

In [146]:
remainder(15,0)

# it loops indefinitely. Press stop in the toolbar to interrupt it.
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-146-388f19cc4859> in <module>()
----> 1 remainder(15,0)
      2 
      3 # it loops indefinitely. Press stop in the toolbar to interrupt it.

<ipython-input-142-5a04b1076572> in remainder(a, b)
      1 def remainder(a,b):
      2     while a >= b:
----> 3         a = a - b
      4     return a

KeyboardInterrupt: 

Fix:

In [161]:
def remainder(a,b):
    if b == 0:
        print("ERROR, can't divide by zero")
        return
        # alternative: you can do:   return None
    while a >= b:
        a = a - b
    return a
In [162]:
remainder(10,0)
ERROR, can't divide by zero


Actually there are other issues:

What if a<0? It causes an infinite loop again because removing b sends us in the wrong direction.

Can we just take absolute values at the beginning? No, because the answer actually changes if you replace a by abs(a), for example: $$15 = 4 \times 3 + 3$$ but $$-15 = 4 \times (-4) + 1$$ because, according to the definition, remainders have to be positive.

Solution: handle the negative case a<0 separately, adding b instead of removing it.

In [170]:
def remainder(a,b):
    if b == 0:
        print("ERRORRR")
    if a >= 0:
        while a >= b:
            a = a - b
    else:
        while a < 0: 
            a = a + b
    return a
In [171]:
remainder(-15,4)
Out[171]:
1
In [174]:
# making sure it still works in the positive case
remainder(15,4)
Out[174]:
3


What if b<0. Actually, the way we wrote down the definiton of the remainder: $$a = qb + r$$ Where $r$ is the unique remainder $$ 0 \leq r < b$$

gives us a problem, because of b<0, then there is no number $b > r \geq 0$. So, let's change the definition: $$a = qb + r$$ Where $r$ is the unique remainder $$ 0 \leq r < | b |$$

According to this definition, replacing $b$ by $-b$ won't change the remainder $r$. (but it will change the quotient $q$, (how?))

So we can change b to its absolute value without changing the remainder, and this would handle the negative case.

In [175]:
def remainder(a,b):
    if b == 0:
        print("ERRORRR")
    b = abs(b)
    if a >= 0:
        while a >= b:
            a = a - b
    else:
        while a < 0: 
            a = a + b
    return a

Good exercise:

  • Implement integer_division(a,b), the integer division function by hand (without using a//b) (i.e. producing the $q$ in $a = bq + r$). Make sure to handle the negative cases well.
In [ ]: