CS 60, Spring 1998

Assignment 10

Power Computing

Due Friday, May 2, 1998

60 points

The purpose of this assignment is to exercise analytic complexity and measurement techniques in a simple example, computing powers of a number. We will also see how such derived results are dependent on assumptions about times of basic operations. This assignment requires mostly analysis; the code is written for you.

In /cs/cs60/c++/powers.cc is the C++ code for two functions for computing powers of a number: slow_power(k, n) computes k to the n-th power in the most obvious way, multiplying k by itself n times; fast_power computes the same function, but uses the "Russian peasant's" algorithm, repeatedly squaring k (to get powers which are powers of 2) and multiplying selected ones of those powers together to get the desired power. The program has already been compiled. The executable is in /cs/cs60/bin/powers. The result of a typical execution on turing is in /cs/cs60/ht/examples/powers.out.

For this assignment we are going to concentrate on computing powers of a fixed number, 7. This removes one degree of freedom. The number of decimal digits in the power is thus directly proportional to the power itself. The function on which we are focused is then T(n), the time required to compute 7n.

We are going to try each function on two different numeric types: double and Integer (not int, and not the same as the Java Integer). Integer multiplication preserves all of the digits in the answer, whereas double truncates the digits to a fixed precision. The assumption of constant multiplication time thus may not be valid for the Integer version.

The program sets up runs for four cases in all:

        {fast, slow} x {double, Integer}
  1. Assuming that the time for multiplication is constant, analyze the two functions fast_power and slow_power to obtain an asymptotic upper bound on the run time as a function of the power n,

  2. Assuming that the time for multiplication is proportional to the product of the lengths of the arguments being multiplied (which is conjectured to be the case for the class Integer), analyze the same two functions to obtain an asymptotic upper bound on the run time.

  3. Run the power routines on varying powers of 7 under both numeric models. Try to find empirically an "O" expression which seems to predict the run-times of the functions. Do this by dividing by appropriate analytic functions of the power, such as n, n*log(n), n*n, etc. as coded in powers.cc. These are suggestions, but you are not limited to them if you want to test your own hypotheses.

    Here is an example of the output for one of the four cases is shown for a sample program:

        Using doubles slow
                                time/
            n 1        log n    n        n log n  n*n      reps
          128 2.00e-05 4.12e-06 1.56e-07 3.22e-08 1.22e-09 1000
          256 5.00e-05 9.02e-06 1.95e-07 3.52e-08 7.63e-10 1000
          512 1.10e-04 1.76e-05 2.15e-07 3.44e-08 4.20e-10 1000
         1024 2.10e-04 3.03e-05 2.05e-07 2.96e-08 2.00e-10 1000
         2048 4.10e-04 5.38e-05 2.00e-07 2.63e-08 9.78e-11 1000
         4096 8.30e-04 9.98e-05 2.03e-07 2.44e-08 4.95e-11 1000
    
    The first column indicates the exponent and the second the time the power function takes on that exponent. The other columns are the second column divided by the analytic expressions indicated. reps indicates the number of repetitions used to get within the timer's resolution.

  4. How well do the empirical timings agree with your analyses? If they don't agree, you may wish to consider revising your previous analyses.

  5. Summarize any overall conclusions for the problem.

Your submission will consist of an analysis of the two programs under two sets of assumptions, and an empirical justification of your analysis.

(Extra credit) Program a way to multiply Integers faster than the one provided in class Integer and show that it really works. I have in mind something like the Karatsuba algorithm, which multiplies two Integers of length n in time n1.5, although this is not the fastest possible.
(Note: You are not restricted to the run-time functions shown in the example if you want to modify the code.)

Below is the code for the two functions. Since these use templates, the type Number will automatically be instantiated by the compiler to either double or Integer depending on which type of variable we give it.


    // compute k to the power n using the obvious method

    template <class Number>
    Number slow_power(Number k, long n)
    {
    Number p = 1;
    while( n-- > 0 )  // loop invariant: p is k to the power n0-n
      p = p * k;      // where n0 is the original value of n
    return p;
    }


    // compute k to the power n using the Russian peasant's method

    template <class Number>
    Number fast_power(Number k, long n)
    {
    Number p = 1;
    while( n > 0 )     // loop invariant: p is k to the power N
      {                // where N is the product of the low-order I
      if( n % 2 == 1 ) // powers of 2 in the binary expansion of n0, with
        p = p * k;     // I being the number of iterations of the loop so far
      n = n/2;
      if( n == 0 )     // avoid computing k*k if done now
        break;
      k = k*k;
      }
    return p;
    }