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

In [3]:

```
fprint(0.1)
```

Out[3]:

In [4]:

```
fprint(pi)
```

Out[4]:

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

In [6]:

```
ffrac_ltx(0.75)
```

Out[6]:

In [7]:

```
ffrac_ltx(0.5232)
```

Out[7]:

In [8]:

```
#This is the maximum number of bits used in the 0.1 Mantissa as a string.
print(ffrac(0.1, 100))
```

In [9]:

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

In [10]:

```
0.1 + 0.2 == (1.0 + 2.0) / 10.0
```

Out[10]:

`==`

with floats!¶In [11]:

```
print(abs(-43))
```

In [12]:

```
x = -5
x = abs(x)
print(x)
```

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))
```

In [14]:

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

In [15]:

```
from math import *
print(e)
```

In [16]:

```
print(2 == 5)
```

In [17]:

```
a = 3
print(a == 3)
```

In [18]:

```
print(a <= 0)
```

In [19]:

```
print(a > 0 and a < 4)
```

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

In [20]:

```
print(True or 1 / 0.)
```

In [21]:

```
print(True and 1 / 0.)
```

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))
```

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))
```

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))
```

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)
```

In [26]:

```
a = b
print(a is b)
```

`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)
```

In [28]:

```
a = 43
b = 23
print(a != b)
```

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')
```

In [30]:

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

In [31]:

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

In [32]:

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

In [33]:

```
x = math.sin(0.0)
if(x):
print('True')
else:
print('False')
```

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

In [34]:

```
x = 0.15 + 0.15
y = 0.1 + 0.2
print(x == y)
```

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

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)
```

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])
```

You can access multiple elements with the `:`

symbol, which is called *slicing*.

In [38]:

```
print(numbers[0:3])
```

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

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])
```

The index arguments to a slice are optional.

In [41]:

```
print(numbers[:4])
```

In [42]:

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

In [43]:

```
print(numbers[3:])
```

In [44]:

```
print(numbers[-3:])
```

You can add a third argument by adding another `:`

It is the step-size.

In [45]:

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

The other slicing indices are optional.

In [46]:

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

In [47]:

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

In [48]:

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

Using a negative stepsize allows you to count downwards

In [49]:

```
print(numbers[4:0:-1])
```

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])
```

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.

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

In [52]:

```
x = range(10)
print(max(x))
```

In [53]:

```
import math
math.cos(x)
```