Spring 2012 CSCI 220 Week 3

Numeric Data Types
Originally computers were seen as number crunchers, which is still an important function today. This week we will take a look at more numerical calculations.

Consider the following program to calculate the value of some change in dollars: def main: print("Change Counter") print print("Please enter the count of each coin type.") quarters = eval(input("Quarters: ")) dimes = eval(input("Dimes: ")) nickels = eval(input("Nickels: ")) pennies = eval(input("Pennies: ")) total = quarters * .25 + dimes * .10 + nickels * .05 + pennies * .01 print print("The total value of your change is", total)

main

Example output of the above function: Change Counter

Please enter the count of each coin type. Quarters: 10 Dimes: 5 Nickels: 3 Pennies: 10

The total value of your change is 3.25

Program above manipulates two different types of numbers. The values entered by the user are whole numbers, but the values of the coins (.25, .10, .05, .01) are decimal representations of factions. Inside of a computer, whole numbers and numbers that have factional components are stored differently. We would say that these are two different data types.

The data type of a variable or object determines what values it can have and what operations can be performed on it:
 * Whole numbers are stored as integers or int for short.
 * Numbers with fractional parts are represented as floating point or float for short.

How can you tell what data type you are looking at? Answer: use the python type function. >>> type(3)  >>> type(3.14)  >>> type(3.0)  >>> myInt = -32 >>> type(myInt)  >>> myFloat = 32.0 >>> type(myFloat) 

Why do we need two different types of numbers?
 * Some numbers represent counts that can't have fractional values (i.e., the number of quarters that you have in your hand)
 * Efficiency of various operators (integer operators are faster)
 * Efficiency and precision of numbers (floats have a limit on the precision, or accuracy, of the stored values)

Built-in operators:
 * + is addition
 * - is subtraction
 * * is multiplication
 * / is division
 * ** is exponentiation
 * abs is absolute value
 * // is integer division
 * % is a remainder

Examples: >>> 3+4 7 >>> 3.0 + 4.0 7.0 >>> 3*4 12 >>> 3.0*4.0 12.0 >>> 4 ** 3 64 >>> 4.0 ** 3 64.0 >>> 4.0 ** 3.0 64.0 >>> abs(5) 5 >>> abs(-3.5) 3.5

One important consideration is division in Python 3: >>> 10 / 3 3.3333333333333335 >>> 10 // 3 3 >>> 10.0 // 3.0 3.0 >>> 10 % 3 1 >>> 10 % 3.0 1.0

The remainder operator tells you how much you have left after integer division. This can be very useful. Consider the question of finding the number of dollars and the remaining cents if I told you I had a total of 383 cents. >>> 383 // 100 3 >>> 383 % 100 83 >>> print("You have ",383 // 100,"dollars and",383 % 100,"cents") You have 3 dollars and 83 cents

Using the Math library

 * 1) quadratic.py
 * 2)   A program that computes the real roots of a quadratic equation.
 * 3)   Illustrates use of the math library
 * 4)   Note: this program crashes if the equation has no real roots

import math # Makes the math library available

def main: print("This program finds the real solutions to a quadratic") print

a, b, c = eval(input("Please enter the coefficients (a, b, c): "))

discRoot = math.sqrt(b*b - 4*a*c) root1 = (-b + discRoot) / (2*a) root2 = (-b - discRoot) / (2*a)

print print("The solutions are:", root1, root2)

main

Example execution: This program finds the real solutions to a quadratic

Please enter the coefficients (a, b, c): 3, 4, -2

The solutions are: 0.38742588672279316 -1.7207592200561266

Imaginary roots: This program finds the real solutions to a quadratic

Please enter the coefficients (a, b, c): 1, 2, 4 Traceback (most recent call last): File "C:/Users/andersonp/Desktop/quadratic.py", line 21, in    main File "C:/Users/andersonp/Desktop/quadratic.py", line 14, in main discRoot = math.sqrt(b*b - 4*a*c) ValueError: math domain error

In addition to sqrt, the math library provides many common functions. To get a list of these functions see Table 3.2 or type math. at the command prompt and then hit tab on your computer. IDLE will give you a list of the functions.

Accumulating Results: Factorial
Suppose you have a six pack containing different types of cola (coke, pepsi, RC cola, etc). How many possible orders are there to drink the sodas? Answer: 6! = 720

n! = n*(n-1)*(n-2) ... (1)

Exercise: Write a pseudo code version of a program to compute the factorial using a accumulator variable and a loop structure.

Answer: Input number to take factorial of, n Initialize accumulator variable, fact for factor in n,n-1,n-2,...,1 fact = fact * factor

The order doesn't matter because multiplication is commutative (2*4 = 4*2) and associative, i.e., 2*(4*3) = (2*4)*3.

Exercise: Now write a Python solution

range(n,1,-1): >>> list(range(6,1,-1)) [6, 5, 4, 3, 2]

One Answer:
 * 1) factorial.py
 * 2)   Program to compute the factorial of a number
 * 3)   Illustrates for loop with accumulator

def main: n = eval(input("Please enter a whole number: ")) fact = 1 # Accumulator for factor in range(n,1,-1): fact = fact * factor print("the factorial of",n,"is",fact)

main

Execution: Please enter a whole number: 6 the factorial of 6 is 720

Limitations of Computer Arithmetic
Factorial can produce very large numbers that can quickly produce problems for some programming languages(e.g., earlier versions of Python and Java):

Python 3 has no problem with 100!: Please enter a whole number: 100 the factorial of 100 is 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

But if we coded up our solution in Java: Please enter a whole number: 6 The factorial is 720
 * 1) run 1 (correct)

Please enter a whole number: 12 The factorial is 479001600
 * 1) run 2 (correct)

Please enter a whole number: 13 The factorial is 1932053504 (THIS IS WRONG)
 * 1) run 3 (incorrect)

Run 3 is wrong. Our Python program tell us that 13! is: Please enter a whole number: 13 the factorial of 13 is 6227020800

What happened?
 * There are infinitely many different integers, but your computer's hardware can only represent a fixed number of those integers.

In your CPU, there are small memory devices known as registers. These registers are simply a set of electrical switches that have two values (on or off). Or more commonly referred to a binary (0 or 1). There are 32 or 64 electrical switches in each register depending on whether you have a 32 bit or 64 bit machine.

Exercise: Ignoring negative numbers for a moment. How big of a number can you represent with 2 bits? With 3 bits?

We need to use a bit for whether a number is positive or negative, so with a 32 bit machine, the biggest number we can represent is: >>> 2**31 - 1 2147483647

Python gets around this problem by using your machines integer representation when the number is small enough, but for large number Python breaks the number down in smaller parts that your computer can handle.

What about floating point numbers?
What do you expect the following code to output? sum = 0.0 for i in range(10): sum += 0.1 print(sum)

Answer: 0.9999999999999999

This problem is called representation error, which refers to the problem that most decimal fractions cannot be represented in exact binary (base 2) fractions. This is because of the way that computers represent fractions. Almost all machines use IEEE-754 floating point arithmetic:

The Sign Bit

The sign bit is as simple as it gets. 0 denotes a positive number; 1 denotes a negative number. Flipping the value of this bit flips the sign of the number.

The Exponent

The exponent field needs to represent both positive and negative exponents. To do this, a bias is added to the actual exponent in order to get the stored exponent. For IEEE single-precision floats, this value is 127. Thus, an exponent of zero means that 127 is stored in the exponent field. A stored value of 200 indicates an exponent of (200-127), or 73. For reasons discussed later, exponents of -127 (all 0s) and +128 (all 1s) are reserved for special numbers.

For double precision, the exponent field is 11 bits, and has a bias of 1023.

The Mantissa

The mantissa, also known as the significand, represents the precision bits of the number. It is composed of an implicit leading bit and the fraction bits.

To find out the value of the implicit leading bit, consider that any number can be expressed in scientific notation in many different ways. For example, the number five can be represented as any of these:
 * 5.00 × 100
 * 0.05 × 102
 * 5000 × 10-3

In order to maximize the quantity of representable numbers, floating-point numbers are typically stored in normalized form. This basically puts the radix point after the first non-zero digit. In normalized form, five is represented as 5.0 × 100.

A nice little optimization is available to us in base two, since the only possible non-zero digit is 1. Thus, we can just assume a leading digit of 1, and don't need to represent it explicitly. As a result, the mantissa has effectively 24 bits of resolution, by way of 23 fraction bits.

So how would you store 0.1?

We need to change bases to binary. We do this by multiply by 2.
 * 0.1 x 2 = 0.2 => 0 and continue
 * 0.2 x 2 = 0.4 => 0 and continue
 * 0.4 x 2 = 0.8 => 0 and continue
 * 0.8 x 2 = 1.2 => 1 and continue
 * 0.2 x 2 = 0.4 => 0 and continue
 * 0.4 x 2 = 0.8 => 0 and continue
 * 0.8 x 2 = 1.2 => 1 and continue
 * 0.2 x 2 = 0.4 => 0 and continue
 * Do you see how this would repeat indefinitely?

One approximation of 0.1 in binary is 0.0001001. We can move the decimal over 4 places, so that we have 1.001 x 2^-4. Our mantissa is 00100000000000000000000 because we have 23 bits, and our exponent is 127 - 4 = 123, because our bias is 127 (2^0 is 127).

Type Conversions and Rounding
Converting floats to integers and integers to floats: >>> float(3) 3.0 >>> int(2.3) 2 >>> float(int(3.3)) 3.0

int just drops off the decimal. If you want to round a decimal in Python, then you can use the round function: >>> round(3.3) 3 >>> round(3.5) 4