# CMU 15-112: Fundamentals of Programming and Computer Science Class Notes: Data and Expressions

1. Some Builtin Types
import math def f(): print("This is a user-defined function") return 42 print("Some basic types in Python:") print(type(2)) # int print(type(2.2)) # float print(type("2.2")) # str (string) print(type(2 < 2.2)) # bool (boolean) print(type(math)) # module print(type(math.tan)) # builtin_function_or_method ("function" in Brython) print(type(f)) # function (user-defined function) print(type(type(42))) # type print("#####################################################") print("And some other types we will see later in the course...") print(type(Exception())) # Exception print(type(range(5))) # range print(type([1,2,3])) # list print(type((1,2,3))) # tuple print(type({1,2})) # set print(type({1:42})) # dict (dictionary or map) print(type(2+3j)) # complex (complex number) (we may not see this type)

2. Some Builtin Constants
print("Some builtin constants:") print(True) print(False) print(None) print("And some more constants in the math module:") import math print(math.pi) print(math.e)

3. Some Builtin Functions
print("Type conversion functions:") print(bool(0)) # convert to boolean (True or False) print(float(42)) # convert to a floating point number print(int(2.8)) # convert to an integer (int) print("And some basic math functions:") print(abs(-5)) # absolute value print(max(2,3)) # return the max value print(min(2,3)) # return the min value print(pow(2,3)) # raise to the given power (pow(x,y) == x**y) print(round(2.354, 1)) # round with the given number of digits

4. Some Builtin Operators
CategoryOperators
Arithmetic+, -, *, /, //, **, %, - (unary), + (unary)
Relational <, <=, >=, >, ==, !=
Assignment+=, -=, *=, /=, //=, **=, %=
Logicaland, or, not
Note: for now at least, we are not covering the bitwise operators (<<, >>, &, |, ^, ~).

5. Types Affect Semantics
print(3 * 2) print(3 * "abc") print(3 + 2) print("abc" + "def") print(3 + "def")

6. Integer Division
print("The / operator does 'normal' float division:") print(" 5/3 =", ( 5/3)) print() print("The // operator does integer division:") print(" 5//3 =", ( 5//3)) print(" 2//3 =", ( 2//3)) print("-1//3 =", (-1//3)) print("-4//3 =", (-4//3))

7. The Modulus or Remainder Operator (%)
print(" 6%3 =", ( 6%3)) print(" 5%3 =", ( 5%3)) print(" 2%3 =", ( 2%3)) print(" 0%3 =", ( 0%3)) print("-4%3 =", (-4%3)) print(" 3%0 =", ( 3%0))

Verify that (a%b) is equivalent to (a - (a//b)*b):
def mod(a, b): return a - (a//b)*b print(41%14, mod(41,14)) print(14%41, mod(14,41)) print(-32%9, mod(-32,9)) print(32%-9, mod(32,-9))

8. Operator Order (Precedence and Associativity)
print("Precedence:") print(2+3*4) # prints 14, not 20 print(5+4%3) # prints 6, not 0 (% has same precedence as *, /, and //) print(2**3*4) # prints 32, not 4096 (** has higher precedence than *, /, //, and %) print() print("Associativity:") print(5-4-3) # prints -2, not 4 (- associates left-to-right) print(4**3**2) # prints 262144, not 4096 (** associates right-to-left)

9. Bitwise Operators
• Binary Numbers
On a computer, numbers are stored in binary (base 2) format. In binary, there are only two digits used to make up numbers: 0 and 1. This means that numbers are stored as a sequence of 0s and 1s.

To understand this, let's look at how counting works in decimal (base 10: the number system you are used to.) Let's count: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. In this simple process of counting, we use all of the digits we have (0-9); there aren't anymore digits. When we run out of digits, we simply add another one and keep going: 10, 11, 12, 13, 14, 15, 16, 17, 18, 19. Next we would increment the one to a two, and then reuse all of our digits again: 20, 21, 22, 23, 24, 25, 26, 27, 28, 29. We can follow this pattern forever, counting.

Binary does the same thing, except there are only two digits. So, if we count in binary: 0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111. Stop for a moment and look at that again to make sure you understand the order of the counting. Remember, it is the same as decimal, except that there are only two different digits instead of ten, so we are adding extra digits a lot more often.

Let's line up the first 16 numbers in both decimal and binary, and include some leading zeroes. (Remember, 07 is the same as 7, it just has an extra 0.)
Decimal Binary
00 0000
01 0001
02 0010
03 0011
04 0100
05 0101
06 0110
07 0111
08 1000
09 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111
This is just counting up in both systems starting from 0. When you put them side-by-side, however, you also get to see how the same number is represented in both systems. The number twelve, for example, is written as "12" in decimal and "1100" in binary.

• Binary Operators
Because computers store all numbers in a binary format, there are also some mathematical operators that work by modifying those binary numbers. Let's look at those operators:
Operator Description Example (This is not Python Code)
<< Left shift. Move all of the bits in the number to the left the specified number of places. Put zeros in the newly empty spaces on the right. (0110 << 1) becomes 1100
(0110 << 2) becomes 11000
>> Right shift. Move all of the bits in the number to the right the specified number of places. Put zeros in the newly empty spaces on the left. (0110 >> 1) becomes 0011
(0110 >> 2) becomes 0001
~ Negation. Flip all of the bits in a number. All zeroes become ones and all ones become zeroes. (~0110) becomes 1001
& And. Take two numbers, and do a bit-wise and operation between them. An and operation takes two bits and outputs one according to the following rules:
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
We apply this to each pair of bits in the two numbers.
(0101 & 0011) becomes 0001
| Or. Take two numbers and do a bit-wise or operation between them. An or operation takes two bits and outputs one according to the following rules:
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
We apply this to each pair of bits in the two numbers.
(0101 | 0011) becomes 0111
^ Exclusive-OR (XOR). Take two numbers and do a bit-wise xor operation between them. An xor operation takes two bits and outputs one according to the following rules:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
We apply this to each pair of bits in the two numbers.
(0101 ^ 0011) becomes 0110
• Binary Operator Examples in Python
a = 6 # 0110 in binary b = 5 # 0101 in binary c = 3 # 0011 in binary print(a << 1) # Outputs 12 (1100 in binary) print(a << 2) # Outputs 24 (11000 in binary) print(a >> 1) # Outputs 3 (0011 in binary) print(a >> 2) # Outputs 1 (0001 in binary) print(b & c) # Outputs 1 (0001 in binary) print(b | c) # Outputs 7 (0111 in binary) print(b ^ c) # Outputs 6 (0110 in binary)

10. Approximate Values of Floating-Point Numbers
print(0.1 + 0.1 == 0.2) # True, but... print(0.1 + 0.1 + 0.1 == 0.3) # False! print(0.1 + 0.1 + 0.1) # prints 0.30000000000000004 (uh oh) print((0.1 + 0.1 + 0.1) - 0.3) # prints 5.55111512313e-17 (tiny, but non-zero!)

Equality Testing with math.isclose:
print("The problem....") d1 = 0.1 + 0.1 + 0.1 d2 = 0.3 print(d1 == d2) # False (never use == with floats!) print() print("The solution...") import math print(math.isclose(d1, d2)) # True! # math.isclose checks if the two numbers are ALMOST equal, within a small error

11. Short-Circuit Evaluation
def yes(): return True def no(): return False def crash(): return 1/0 # crashes! print(no() and crash()) # Works! print(crash() and no()) # Crashes! print (yes() and crash()) # Never runs (due to crash), but would also crash (without short-circuiting)

Once again, using the "or" operator:
def yes(): return True def no(): return False def crash(): return 1/0 # crashes! print(yes() or crash()) # Works! print(crash() or yes()) # Crashes! print(no() or crash()) # Never runs (due to crash), but would also crash (without short-circuiting)

Yet another example:
def isPositive(n): result = (n > 0) print("isPositive(",n,") =", result) return result def isEven(n): result = (n % 2 == 0) print("isEven(",n,") =", result) return result print("Test 1: isEven(-4) and isPositive(-4))") print(isEven(-4) and isPositive(-4)) # Calls both functions print("----------") print("Test 2: isEven(-3) and isPositive(-3)") print(isEven(-3) and isPositive(-3)) # Calls only one function!

12. type vs isinstance
# Both type and isinstance can be used to type-check # In general, (isinstance(x, T)) will be more robust than (type(x) == T) print(type("abc") == str) print(isinstance("abc", str)) # We'll see better reasons for this when we cover OOP + inheritance later # in the course. For now, here is one reason: say you wanted to check # if a value is any kind of number (int, float, complex, etc). # You could do: def isNumber(x): return ((type(x) == int) or (type(x) == float)) # are we sure this is ALL kinds of numbers? print(isNumber(1), isNumber(1.1), isNumber(1+2j), isNumber("wow")) # But this is cleaner, and works for all kinds of numbers, including # complex numbers for example: import numbers def isNumber(x): return isinstance(x, numbers.Number) # works for any kind of number print(isNumber(1), isNumber(1.1), isNumber(1+2j), isNumber("wow"))