Today: bluescreen, float, comprehensions
Bluescreen Logic
- Detect blue pixels vs. average of red/green/blue
Tune "* 1.0" factor so it looks best
- Replace blue pixels with pixels from background
Bluescreen Art Contest (optional!)
- We'll have optional HW7c art project you can do for fun
bluescreen
Slight extra credit, not affecting curve
Really just for fun after you're done with HW7
Art show in class, prizes
- Recall - Bluescreen algorithm
- 1. "front" strategy
Detect blue pixels - average technique
Replace blue pixels in image with background pixels
Demo here - comment out the pixel.red = xxx lines to see original image
- 2. Alternate "back" strategy
Detect non-blue (i.e. monkey) pixels
Copy them to back image
>
bluescreen-monkey
Also a PyCharm project with monkey example:
bluescreen.zip. This example shows both "front" (above) and back strategy.
Aside: HP 35 Calculator
- Pre computer, had the slide rule
- Floating point calculation was just possible in 1970
- A large, expensive desktop machine
- 1970: Bill Hewlett said .. it would be great if you could switch this huge desktop calculator down so it fits in my pocket
- That was a huge jump in capability to ask for
- Silicon valley: genius or hubris!
- They did it HP 35 calculator HP35 1972
- Moore's law - chips technology just getting started, enabled HP35
- The iPhone of its day
- You can buy one on eBay - red glowing numbers, LCD not invented yet
Two Math Systems, "int" and "float"
- Two Systems
- int and float are two different worlds
- "float" .. floating decimal point, moves around
- Float and int - each have their own area on the chip
- Look similar, but distinct
- int e.g.
# int
3 100 -2
# float, has a "."
3.14 -26.2 6.022e23
Math Works
- Math works: + - * / min() max() for both int and float fine:
- i.e. mostly don't have to think about it
- Foreshadow:
Float mostly works easily
BUT Float has one crazy flaw .. revealed below
- Clickbait: you will not believe what float does
1. int, int + int = int
- e.g. 3 1001 -26
- int is very common because indexing into a structure is fundamentally int
- + - * % // ** work as we've seen
- Math with int inputs, yields int result
- int in, int out
>>> 1 + 2
3
>>> type(3)
>>> 10001 - 24
9977
>>> 10001 // 10
1000
>>> 10001 % 10
1
>>> 10 ** 3
1000
One exception: / yields a float - crosses the two worlds
>>> 5 // 2 # // int division, no fraction
2
>>> 5 / 2 # / division, yields float
2.5
float
- e.g.
3.14, 1.2e16
- In Python, including a dot "." makes it a "float" type
- dot "." in input and output
- e.g. 3.0 is a float of the number three
- e.g. 3 is an int of the number three
- Division / yields a float (for int division use //)
>>> 7 / 2 # get float
3.5
>>> 7 // 2 # get int
3
float Arithmetic: float + float = float
- float math largely follows existing math patterns
+ - * / ** .. all work with floats
- float in, float out
- Math with float inputs, yields float result
- Note the "." in the output in every case
>>> 3.14
3.14
>>> type(3.14)
>>>
>>> 3.14 * 2.0
6.28
>>> 3.14 * 2.0 + 1.0
7.28
>>> 10.0 ** 6.0
1000000.0
float Scientific Notation "3.14e26"
- Float numbers can span an enormous range
- Like your TI84 calculator (or HP 12c!)
- Scientific format:
3.14e26
above 3.14 * 1026
- The 3.14 is the "fraction" with about 15 digits of accuracy
- The e26 "exponent" can be in the range 10 to the -308..+308
- In Python, using "e26" format yields a float type number
- Lots of physics, chemistry, 3D graphics .. works well with this sort of number
Mixed Case: int + float = float "promotion"
- Mixed case: int + float
- Combine int and float .. yields float
- Any float value "promotes" the computation to float
- Note how output below is all float, some int inputs
>>> 1 + 1 + 1.0
3.0
>>> 3.14 * 2
6.28
>>> 3.14 * 2 + 1
7.28
float() int() Conversions
-Use float() to convert str to float value, similar to int()
>>> float('3.14') # str -> float
3.14
>>> int(3.14) # float -> int, truncation
3
>>> int('16')
16
>>> int('3.14')
ValueError: invalid literal for int() with base 10: '3.14'
int for Indexing, Not float
- Use int to index into strs, images, ...
- Floats do not work for indexing/slicing
- Fits the "two worlds" interpretation, indexing is int
>>> lst = ['a', 'b', 'c', 'd']
>>> mid = len(lst) // 2 # int division
>>> mid
2
>>> lst[mid] # fine
'c'
>>> mid = len(lst) / 2 # float
>>> mid
2.0
>>> lst[mid]
TypeError: list indices must be integers or slices, not float
Float Applications
- Float is useful where need digits to the right of the "."
- A smoothly varying function
- e.g. on babygraphics computed x coord for line, based on 0.0..1.0 fraction
babygraphics window width 400, 800, 1200 pixels divided evenly
- e.g. Need a big range
- e.g. float used for angles, velocities, etc. for 3D graphics (games!)
- Float can be slower than int in the CPU
- In Python the speeds are roughly similar
Float - One Crazy Flaw - 1/10
- Note: do not panic! We can work with this. But it is shocking.
- Float arithmetic is a little imprecise
- Off at the 15th digit .. there are erroneous "garbage" digits
- 1. Idea of 1/10th, mathematically pure
- 2. In Python code: looks like this
0.1
- 3. In the computer memory, Python stores the number like this:
0.100000000000076
- There are some garbage digits way off to the right
- The Math Will Not Come Out Exactly Right
- This is a deep feature of computer floats, applies to all languages
- The print routine hides a few digits, so often the garbage is hidden
But in the computations, the garbage is there
Crazy Flaw Demo - Adding 1/10th
- Garbage digits are very often part of a float value
- Printing omits a few stored digits at right
- So often do not see the garbage
- But eventually the garbage gets big enough to print...
>>> 0.1
0.1
>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1 # everyone contemplate this
0.30000000000000004
>>>
>>> 0.1 + 0.1 + 0.1 + 0.1
0.4
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.5
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.6
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7999999999999999 # here the garbage is negative
Another example with 3.14
>>> 3.14 * 3
9.42
>>> 3.14 * 4
12.56
>>> 3.14 * 5
15.700000000000001 # this is why we can't have nice things
Summary: float math is slightly wrong
Why? Why Must There Be This Garbage?
- We think in base 10
- So 0.1 and 2.5 come out "even"
- But 1/3 does not come out even 0.333333333...
- Some numbers come out even in base 10, and some don't
- The computer uses base 2 internally - 0's and 1's
- In base 2, many numbers don't come out even
- There's this little garbage error off to the right
- Base 10 has the 1/3 case, but you're just used to that one
Crazy, But Not Actually A Problem
- Everyone needs to remember:
- float arithmetic always comes out a tiny bit wrong
- (int arithmetic, comes out perfect)
- The error is typically far less than 1-trillionth part
- But the error is not zero
- Most computations can handle an error of 1-trillionth part
- Actually not a problem
- How many digits of accuracy in the inputs, 6 digits?
Must Avoid One Thing: no ==
- There is one concrete coding rule
- Do not use == with float
>>> a = 3.14 * 5
>>> b = 3.14 * 6 - 3.14
>>> a == b # Observe == not working right
False
>>> b
15.7
>>> a
15.700000000000001
- abs(x) - the absolute value function
- Instead of ==, look at abs(a-b)
>>> abs(a-b) < 0.00001
True
- Exception: 0.0 is reliable for ==
- Any float value * 0.0 will be exactly 0.0
Float Conclusions
- 1. Two systems, int 67 and float 3.14
- 2. Math works for both int/float seamlessly
- 3. Float has tiny error many digits to the right, don't use ==
List Comprehensions - Magic
- Comprehensions are like map(), but pretty
- Comprehensions, when they click, feel a bit magical
- We did map() because you must know lambda
- It's fine to use comprehensions instead of map()
- If you work as a Python intern .. they will be pleased you know comprehensions
- Have list of XXX want list of YYY
- Pattern that appears in code 15% of the time
- Python's solution is beautiful for that 15%
Comprehension Syntax 1
- Given a list of elements
- Compute a new list, populated:
- expression: old elem → new elem
- e.g. compute square of each num element
- Syntax:
>>> nums = [1, 2, 3, 4, 5, 6]
>>> [n * n for n in nums]
[1, 4, 9, 16, 25, 36]
>>> [n * -1 for n in nums]
[-1, -2, -3, -4, -5, -6]
- Computes a new list, original is unchanged
- Nick's mnemonic: re-use syntax of other features
- 1-2-3 steps to write a comprehension:
1. type in a pair of outer brackets [ ]
2. inside write a foreach "for n in nums"
3. then the result expression "n * n" goes on the left
Comprehension If 2
- Can add "if" filter on the right hand side
Mnemonic: re-use syntax again
- Left hand side can be just "n" to pass value through unchanged
>>> [n for n in nums if n > 3]
[4, 5, 6]
>>> [n * n for n in nums if n > 3]
[16, 25, 36]
Comprehension Type Change 3
- Suppose you want list of strs ['1!', '2!', ...]
- Expression on left hand side (LHS) can build any type
>>> [str(n) + '!' for n in nums]
['1!', '2!', '3!', '4!', '5!', '6!']
>>> [(n, n) for n in nums]
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]
Comprehension Style Notes
- 1. Name var for what is in the list
e.g. n, s, pair, lst
Keep your mind straight what type you have
- 2. Comprehension Fever
Sometimes people go crazy, trying to write everything is a comprehension
e.g. whole program is 4 nested comprehensions
Not readable!
Is this like Gold Fever?
1 line comprehensions are the sweet spot
You Try It - Comprehensions
These are all 1-liner solutions with comprehensions. We'll just do 1 or 2 of these.
Syntax - e.g. make a list of squares:
[n * n for n in nums]
> Comprehensions