CS303E Homework 2

Instructor: Dr. Bill Young
Due Date: Tuesday, September 10, 2024 at 11:59pm

Copyright © William D. Young. All rights reserved.

Karatsuba Multiplication

Multiplication is one of the most common operations in computing, but also one of the more expensive. The algorithm you learned in grade school for multiplying two n-digit numbers x and y uses a number of operations proportional to n2. You have to multiply each digit of x by each digit in y.

In 1960, the great Russian mathematician Andrey Kolmogorov conjectured that the traditional algorithm was optimal, meaning that any algorithm for that task would require order n2 elementary operations. Within a week, a 23-year old student Anatoly Karatsuba (shown above) found a more efficient algorithm, disproving Kolmogorov's conjecture. If you think that what you do as a student can't have a lasting impact, think again!

Kolmogorov published the method in 1962, in the Proceedings of the USSR Academy of Sciences. The article contained two results on multiplication, Karatsuba's algorithm and a separate result by Yuri Ofman; it listed "A. Karatsuba and Y. Ofman" as the authors. Karatsuba himself wasn't even aware of the paper until he received the reprints from the publisher. Google "Karatsuba Multiplication" to see the complete algorithm and discussion of its complexity.

Your Assignment

In this assignment, you'll implement a special case of Karatsuba Multiplication for two 4-digit integers supplied by the user. This requires several straightforward arithmetic operations. The steps are below.

Hint: when a step (in this algorithm) says to divide or asks for the quotient, that means integer division. And when it asks for the remainder, that means use the remainder operation. For example, the quotient of 19 divided by 3 is (19 // 3) and the remainder is (19 % 3). If you need both, you can do it in two steps.

  1. Ask the user to supply two 4-digit integers. (Information on how to do that is below.) Save the numbers in variables n1 and n2.
  2. Divide n1 by 100; store the quotient in variable a and the remainder in variable b.
  3. Divide n2 by 100; store the quotient in variable c and the remainder in variable d.
  4. Multiply a by c; store the result in variable e.
  5. Multiply b by d; store the result in variable f.
  6. Compute (a + b) * (c + d); store the result in variable g.
  7. Subtract (e + f) from g; store the result in variable h.
  8. Add four zeros to the end of e (i.e., multiply e by 104); store the result in variable i.
  9. Add two zeros to the end of h (i.e., multiply h by 102); store the result in variable j.
  10. Add i, j and f together; store the result in variable k.
  11. Variable k contains the answer.
  12. Finally, multiply n1 and n2 together; store the result in variable ans. It should match what's in k.
For example, if you entered numbers 5678 and 1234, you should have the following values:
  n1 = 5678
  n2 = 1234
  a = 56
  b = 78
  c = 12
  d = 34
  e = 672
  f = 2652
  g = 6164
  h = 2840
  i = 6720000
  j = 284000
  k = 7006652
  ans = 7006652
Think about what steps 2 and 3 accomplished. Can you generalize this?

How to ask for user input: In your program you'll need to prompt the user to enter two 4-digit integers, using two input statements. The input function is not covered until slideset 3, but it's pretty easy. To get these values into variables n1 and n2, include the following two statements:

n1 = int( input("Enter a 4-digit integer: ") )
n2 = int( input("Enter a 4-digit integer: ") )
When the first statement is executed, it will print the prompt "Enter a 4-digit integer: " on the screen, and wait for the user to enter a number. After the user types in a number, say 5678, this number is read by the program as a string, "5678". (The value returned by an input statement is always a string.) Then the function int() will convert that string back into an integer which will be stored in the variable n1. Then, you'll do the same for n2. From there you're good to go.

You can assume that the two numbers entered will represent positive 4-digit integers. Your program doesn't have to check that and doesn't have to work if the user enters bad values. We won't test your program on illegal values.

Don't use eval(): The book sometimes uses the function eval() to convert a string into an integer or float. This function is considered dangerous, especially when applied to user input. eval() passes its argument to the Python interpreter, and a malicious (or careless) user could input a command string that could:

  1. delete all of your files,
  2. take over your machine, or
  3. some other horrible thing.
Use int() or float() when you want to convert a string input into one of these types.

Sample Output

After you have computed k and ans, you are ready to print out the result in the format illustrated below. Your session should look exactly like the following (assuming the user enters numbers 5678 and 1234). Of course, your program should work correctly for any two 4 digit numbers. (Actually, it should work for any integers of 4 or fewer digits.) Any deviations in the format of the output will result in points being deducted.

> python Karatsuba.py 
Enter a 4-digit integer: 5678
Enter a 4-digit integer: 1234
Computed product: 7006652
Expected product: 7006652

Is It Worth It?

It might seem that this is a pretty complicated procedure for multiplication. Is it really simpler than your familiar grade school method? In particular, you still seem to be doing multiplications in computing e, f, g, i and j. The trick is that you're multiplying shorter numbers. You could build a table to store all multiplications of 2-digit integers and then replace the computations for e, f, and g by table lookup, which is very fast. The computations for i and j aren't really multiplications at all, but just shifting the numbers over and adding zeros. That's extremely easy for binary numbers, which is what computers actually work with.

The general Karatsuba algorithm is actually recursive; to multiply two n-digit numbers, you first split the numbers in half, yielding four n/2-digit numbers. Then when you're multiplying those, you apply Karatsuba's algorithm recursively, and so on. Instead of n2 elementary operations, this requires appoximately n1.58 operations. We'll talk about recursion later in the semester. For now, you'll have to trust me that this works and yields a substantial improvement in the efficiency of multiplication, especially for large integers.

By the way, Python allows integers of arbitrary size. The built-in integer multiplication routine in Python uses the "grade-school" algorithm if the integers have at most 70 digits and Karatsuba otherwise.

Turning in the Assignment:

The program should be in a file named Karatsuba.py. Submit the file via Canvas before the deadline shown at the top of this page. Submit it to the assignment hw2 under the assignments sections by uploading your python file.

Your file must compile and run before submission. It must also contain a header with the following format:

# File: Karatsuba.py
# Student: 
# UT EID:
# Course Name: CS303E
# 
# Date:
# Description of Program: 

If you submit multiple times to Canvas, it will rename your file name to something like Karatsuba-1.py, Karatsuba-2.py, etc. Don't worry about that; we'll grade the latest version.

Programming Tips:

Many assignments this semester will include this section called Programming Tips. In general, these are not hard requirements about this specific assignment; they are suggestions to help you become better programmers. So read and apply them!

Naming Variables: It's typically considered lousy programming practice to use single letter variable names. It's almost always much better to use descriptive names. However, the variables in this problem don't have obvious individual meanings, so go ahead and just use the variable names given.

Commenting: it's always a good idea to comment any part of your code that would be unclear to someone reading it later. However, the individual steps of this algorithm are individually clear, but collectively murky. That is, it's very easy to see what each step does; it's much murkier how they work together to multiply. So there's no need to comment each individual step. But there needs to be an overall comment that says what the algorithm accomplishes.

Output format: As explained in slide set 1, there are multiple ways to run your program. If you run in interactive mode (in the Python loop), the system will automatically display the result of every command (unless the result is None). If that result is a string, it will display with string quotes ('answer'). If you're running the program in batch mode (from the command line, as e.g. python myProgram.py) or explicitly print a string, it won't appear with string quotes. If you run your program in batch mode the only things you'll see displayed are the things you explicitly print; it won't display the results of individual commands. The following is in interactive mode:

>>> string = "my string"
>>> string
'my string'
>>> print(string)
my string
>>> 
If your string output doesn't match what's shown in the assignment sample output because of the presence or absence of string quotes, that may be the reason. Usually, it's not an issue to worry about.

But remember, you must turn in a file that contains the code to do this computation. So it's not adequate to just run the steps in interactive mode. You can do that while you're debugging the steps; but you need a complete program stored in a file for your submission.

Debugging with print statements: Many times over the course of this semester, you'll be asking yourself or others: What is wrong with my program? A good way to figure that out is to add print statements. The current program for example, has 12 steps you have to implement. If you wait until you've completed all 12 and then get the wrong answer, it's pretty hard to figure out where you went wrong. But we showed you above, for one specific example, the value of every intermediate result. So if you print the intermediate results as you generate them, you can focus in on the spot where your computations went wrong. A statement like:

   print("a =", a)
right after you've computed a will tell you if you're on the right track. It's only a small amount of effort to add the print statements, but it may save you a lot of trouble later on. Note that it's often especially useful to print out the parameters of a function call to see if the values you thought were passed were actually the values passed. Of course, don't forget to comment out or remove the extra print statements before you submit your assignment. To comment out a statement, just add a # character at the front of the line.