# Python Basics¶

## Unit 3, Lecture 2¶

Numerical Methods and Statistics

# 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 :
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 :
fprint(4.3)

Out:
$\displaystyle 0.5375 \times 2^{3}$
In :
fprint(0.1)

Out:
$\displaystyle 0.8 \times 2^{-3}$
In :
fprint(pi)

Out:
$\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 :
ffrac_ltx(0.5)

Out:
$\displaystyle \frac{ 1 }{ 2^{ 1 } } = 0.5$
In :
ffrac_ltx(0.75)

Out:
$\displaystyle \frac{ 1 }{ 2^{ 1 } } + \frac{ 1 }{ 2^{ 2 } } = 0.75$
In :
ffrac_ltx(0.5232)

Out:
$\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 :
#This is the maximum number of bits used in the 0.1 Mantissa as a string.
print(ffrac(0.1, 100))

0001100110011001100110011001100110011001100110011001101

In :
print('{:0.28f}'.format(0.1))

0.1000000000000000055511151231

In :
0.1 + 0.2 == (1.0 + 2.0) / 10.0

Out:
False

# Using Functions and Modules¶

In :
print(abs(-43))

43

In :
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 :
from math import fabs
print(fabs(-4.34322))

4.34322

In :
from math import fabs, sin, cos
print(fabs(sin(4) * cos(43.)))

0.4201111317694318

In :
from math import *
print(e)

2.718281828459045


# Boolean Logic¶

In :
print(2 == 5)

False

In :
a = 3
print(a == 3)

True

In :
print(a <= 0)

False

In :
print(a > 0 and a < 4)

True


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

In :
print(True or 1 / 0.)

True

In :
print(True and 1 / 0.)

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
----> 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 :
x = 65
if x > 43:
print('Surprise! {} is greater than 43'.format(x))

Surprise! 65 is greater than 43

In :
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 :
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 :
a = 28238
b = 28238
print(a is b, a == b)

False True

In :
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 :
var = None
print(var is None)

True

In :
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 :
x = 1
if(x):
print('True')
else:
print('False')

True

In :
x = None
if(x):
print('True')
else:
print('False')

False

In :
x = 'Hello'
if(x):
print('True')
else:
print('False')

True

In :
x = ''
if(x):
print('True')
else:
print('False')

False

In :
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 :
x = 0.15 + 0.15
y = 0.1 + 0.2
print(x == y)

False


# The solution?¶

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

In :
x = 0.15 + 0.15
y = 0.1 + 0.2
abs(x - y) < 10**-8

Out:
True

# Lists¶

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

In :
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 :
print(numbers, numbers)

4 7


## Slicing Lists¶

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

In :
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 :
numbers[2:5]

Out:
[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 :
print(numbers)
print(numbers[-1], numbers[-2])

[4, 7, 24, 11, 2]
2 11


The index arguments to a slice are optional.

In :
print(numbers[:4])

[4, 7, 24, 11]

In :
print(numbers)
print(numbers[:-1])

[4, 7, 24, 11, 2]
[4, 7, 24, 11]

In :
print(numbers[3:])

[11, 2]

In :
print(numbers[-3:])

[24, 11, 2]


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

In :
print(numbers)
print(numbers[0:-1:2])

[4, 7, 24, 11, 2]
[4, 24]


The other slicing indices are optional.

In :
print(numbers[0::2])

[4, 24, 2]

In :
print(numbers)
print(numbers[::2])

[4, 7, 24, 11, 2]
[4, 24, 2]

In :
print(numbers[2::2])

[24, 2]


Using a negative stepsize allows you to count downwards

In :
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 :
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 :
x = range(100)
len(x)

Out:
100
In :
x = range(10)
print(max(x))

9

In :
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