Python Basics

Unit 3, Lecture 2

Numerical Methods and Statistics


Prof. Andrew White, February 6, 2018


Suggested Reading

  1. https://docs.python.org/3.6/tutorial/
  2. https://en.wikipedia.org/wiki/Floating-point_arithmetic#Floating-point_numbers

Floating Point Representation

Floating point numbers are represented with a mantissa and exponent: $$\underbrace{0.3423}_{\textrm{mantissa}}\times{}\underbrace{10^{-2}}_{\textrm{exponent}}$$


We are working with computers though, so the mantissa is a binary and the exponent is in powers of 2. Let's look at them as decimals to begin though.

In [1]:
from IPython.display import Math
from math import frexp, pi
import math
    
#Convert a float into its mantissa and exponent and print as LaTeX
def fprint(x):
    m,e = frexp(x)
    return Math('{:4} \\times 2^{{{:}}}'.format(m, int(e)))

#Convert a mantissa from decimal to binary and print as LaTeX
def ffrac_ltx(x, terms=10):
    bits = []
    exp = 1
    latex = ''
    while x > 0 and exp < terms:
        bits.append(int(x / 2.0 ** -exp))
        x -= bits[-1] * 2.0 ** -exp
        latex += '\\frac{{ {} }}{{ 2^{{ {} }} }} + '.format(bits[-1], exp)
        exp += 1
    return Math(latex[:-3] + ' = {}'.format(sum([b * 2**-i for b,i in zip(bits,range(1,exp))])))

def ffrac(x, terms=10):
    bits = []
    exp = 1
    while x > 0 and exp < terms:
        bits.append(int(x / 2.0 ** -exp))
        x -= bits[-1] * 2.0 ** -exp
        exp += 1
    return ''.join(['{}'.format(x) for x in bits])
In [2]:
fprint(4.3)
Out[2]:
$\displaystyle 0.5375 \times 2^{3}$
In [3]:
fprint(0.1)
Out[3]:
$\displaystyle 0.8 \times 2^{-3}$
In [4]:
fprint(pi)
Out[4]:
$\displaystyle 0.7853981633974483 \times 2^{2}$

A 'decimal' in binary is challenging to think about, because each integer location is a $1 / 2^{n}$, where $n$ is the location. That means to represent a binary-mantissa exacly, its denominator must be a power of $2$. For example:

In [5]:
ffrac_ltx(0.5)
Out[5]:
$\displaystyle \frac{ 1 }{ 2^{ 1 } } = 0.5$
In [6]:
ffrac_ltx(0.75)
Out[6]:
$\displaystyle \frac{ 1 }{ 2^{ 1 } } + \frac{ 1 }{ 2^{ 2 } } = 0.75$
In [7]:
ffrac_ltx(0.5232)
Out[7]:
$\displaystyle \frac{ 1 }{ 2^{ 1 } } + \frac{ 0 }{ 2^{ 2 } } + \frac{ 0 }{ 2^{ 3 } } + \frac{ 0 }{ 2^{ 4 } } + \frac{ 0 }{ 2^{ 5 } } + \frac{ 1 }{ 2^{ 6 } } + \frac{ 0 }{ 2^{ 7 } } + \frac{ 1 }{ 2^{ 8 } } + \frac{ 1 }{ 2^{ 9 } } = 0.521484375$
In [8]:
#This is the maximum number of bits used in the 0.1 Mantissa as a string.
print(ffrac(0.1, 100))
0001100110011001100110011001100110011001100110011001101
In [9]:
print('{:0.28f}'.format(0.1))
0.1000000000000000055511151231
In [10]:
0.1 + 0.2 == (1.0 + 2.0) / 10.0
Out[10]:
False

Do not use == with floats!

Using Functions and Modules

In [11]:
print(abs(-43))
43
In [12]:
x = -5
x = abs(x)
print(x)
5

Imports and Modules

Try typing math.fa and hit TAB in ipython notebook. You should see suggestions. Next, type math.fabs( and hit TAB and ipython notebook will tell you what the function does.

In [13]:
from math import fabs
print(fabs(-4.34322))
4.34322
In [14]:
from math import fabs, sin, cos
print(fabs(sin(4) * cos(43.)))
0.4201111317694318
In [15]:
from math import *
print(e)
2.718281828459045

Boolean Logic

In [16]:
print(2 == 5)
False
In [17]:
a = 3
print(a == 3)
True
In [18]:
print(a <= 0)
False
In [19]:
print(a > 0 and a < 4)
True

Boolean Logic uses Short-Circuiting - only as much as necessary is evaluated.

In [20]:
print(True or 1 / 0.)
True
In [21]:
print(True and 1 / 0.)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-21-8b897adc5aff> in <module>
----> 1 print(True and 1 / 0.)

ZeroDivisionError: float division by zero

Flow with Boolean

You can make code only accessible if a condition is true with if. You can also use else and elif for alternatives.

To indicate where your if code is located, press tab (which adds 4 spaces).

In [22]:
x = 65
if x > 43:
    print('Surprise! {} is greater than 43'.format(x))
Surprise! 65 is greater than 43
In [23]:
x = 54
if x < 0:
    print('{} is negative'.format(x))
elif x > 0:
    print('{} is positive'.format(x))
else:
    print('{} is 0'.format(x))
54 is positive
In [24]:
x = -32
if x < 0:
    if abs(x) > 5:
        print('{} is less than 0 and has magnitude greater than 5'.format(x))
    else:
        print('{} is not interesting'.format(x))
        
#or you can do:
if x < 0 and abs(x) > 5:
  print('{} is less than 0 and has magnitude greater than 5'.format(x))
if x < 0 and not abs(x) > 5:
        print('{} is not interesting'.format(x))
-32 is less than 0 and has magnitude greater than 5
-32 is less than 0 and has magnitude greater than 5

Full list of boolean operators

Here are the boolean operators: >, <, >=, <=, ==, !=, is

Recall our keywords from Lecture 1: and, or, and not

is is used to see if a variables is None or if two variables point to the same memory address.

In [25]:
a = 28238
b = 28238
print(a is b, a == b)
False True
In [26]:
a = b
print(a is b)
True

is None is special case. None is used as a "sentinel" value to indicate if something is not ready or invalid.

In [27]:
var = None
print(var is None)
True
In [28]:
a = 43
b = 23
print(a != b)
True

Default Boolean - Don't Rely on this!

Just to be aware of, Python has a way of checking if an object is False based on its value (0 or None).

In [29]:
x = 1
if(x):
    print('True')
else:
    print('False')
True
In [30]:
x = None
if(x):
    print('True')
else:
    print('False')
False
In [31]:
x = 'Hello'
if(x):
    print('True')
else:
    print('False')
True
In [32]:
x = ''
if(x):
    print('True')
else:
    print('False')
False
In [33]:
x = math.sin(0.0)
if(x):
    print('True')
else:
    print('False')
False

It is better to just be explicit and give a full boolean expression. You'll most often see these used by accident

Floating Point Boolean

In [34]:
x = 0.15 + 0.15
y = 0.1 + 0.2
print(x == y)
False

The solution?

Only use <, >. Never use == with boolean

In [35]:
x = 0.15 + 0.15
y = 0.1 + 0.2
abs(x - y) < 10**-8
Out[35]:
True

Lists

In python, groups of values can be stored together into lists, which are like groups of variables.

In [36]:
numbers = [4,7,24,11,2]
print(numbers)
[4, 7, 24, 11, 2]

Individual elements can be accessed with []. The thing that goes inside the brackets is called the index. Recall that python starts counting from 0

In [37]:
print(numbers[0], numbers[1])
4 7

Slicing Lists

You can access multiple elements with the : symbol, which is called slicing.

In [38]:
print(numbers[0:3])
[4, 7, 24]

The last element to the slice operator is not inclusive, so 0:3 means 0, 1, 2. Why? Becuase you expect $3 - 0$ elements, which is three, starting from the $0$ index.

In [39]:
numbers[2:5]
Out[39]:
[24, 11, 2]

If it's more convienent, you can count backwards from the end of the list by using negative indices. The trick is that -0 is not really a thing, so the last element in the list is -1.

In [40]:
print(numbers)
print(numbers[-1], numbers[-2])
[4, 7, 24, 11, 2]
2 11

The index arguments to a slice are optional.

In [41]:
print(numbers[:4])
[4, 7, 24, 11]
In [42]:
print(numbers)
print(numbers[:-1])
[4, 7, 24, 11, 2]
[4, 7, 24, 11]
In [43]:
print(numbers[3:])
[11, 2]
In [44]:
print(numbers[-3:])
[24, 11, 2]

You can add a third argument by adding another : It is the step-size.

In [45]:
print(numbers)
print(numbers[0:-1:2])
[4, 7, 24, 11, 2]
[4, 24]

The other slicing indices are optional.

In [46]:
print(numbers[0::2])
[4, 24, 2]
In [47]:
print(numbers)
print(numbers[::2])
[4, 7, 24, 11, 2]
[4, 24, 2]
In [48]:
print(numbers[2::2])
[24, 2]

Using a negative stepsize allows you to count downwards

In [49]:
print(numbers[4:0:-1])
[2, 11, 24, 7]

Note that we still include the first slice index (4), but not the second one (0).

A common idiom is to reverse the order of list this way:

In [50]:
print(numbers, numbers[::-1])
[4, 7, 24, 11, 2] [2, 11, 24, 7, 4]

Notice that Python was clever and knew with a step-size of -1, we actually wanted to start at the far end and count downwards.

List Functions

Generally, most functions (like cos) do not take a list. One common function that we need is len, which returns the length of a list.

In [51]:
x = range(100)
len(x)
Out[51]:
100
In [52]:
x = range(10)
print(max(x))
9
In [53]:
import math
math.cos(x)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-87cdf88e21ce> in <module>
      1 import math
----> 2 math.cos(x)

TypeError: must be real number, not range