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


  1. Some Builtin Types
  2. Some Builtin Constants
  3. Some Builtin Functions
  4. Some Builtin Operators
  5. Types Affect Semantics
  6. Integer Division
  7. The Modulus or Remainder operator (%)
  8. Operator Order (Precedence and Associativity)
  9. Bitwise Operators
  10. Approximate Values of Floating-Point Numbers
  11. Short-Circuit Evaluation
  12. type vs isinstance

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