Project 1: Big Numbers in Little Scheme

2007Sp CS61C
TA: Matt Johnson
Due Sunday, Feb 11, 2007 @ 11:59:59pm
Last updated: Thursday, Feb 08, 2007

Updates

Introduction

Does anyone else miss Scheme and CS61A? Ah, the good ol' days, when we didn't worry about low-level number representation with its imprecisions and limitations. We could call expt without worrying about overflow, or take fractional parts in stride:

   STk> (expt 2 100)
   1267650600228229401496703205376
   STk> (/ 1 5)
   0.2
As Scheme programmers, we only cared about numbers, not fixed-bit-length ints, chars, and unsigned shorts. Well, grab your parentheses, because we want you to implement "bignums" (a datatype and some arithmetic operations) in a very small and simple Scheme interpreter called microscheme! (without using any C bignum libraries, of course...)


Assignment

We want the provided microscheme interpreter to be able to input, output, and do arithmetic on decimal positional notation numbers that can have sign and both whole and fractional parts (each arbitrarily sized). There should be no hard-coded limit to number length. We also want to implement the following arithmetic operations on bignums: + (addition), - (subtraction), * (multiplication), and expt (exponentiation), though you only have to be able to raise a bignum to a non-negative integer power.

Note that we do want you to implement "bignum fractions" that can handle any size fractional part. This implementation will be even better than STk's bignums, because STk can't handle really big fractions:

   STk> .9999999999999999999999999999999999999
   1.0
We don't want any rounding in our microscheme interpreter, so it should respond like this:
   > .9999999999999999999999999999999999999
   0.9999999999999999999999999999999999999
The project can be broken down into the following steps.

(0) Create a bignum data type

The interpreter currently assumes that all numbers can be represented within C's int data type, but that just won't do. You must extend it to be able to internally represent numbers of arbitrary size, possibly with fractional parts: enter the bignum.

You will have to create a bignum data type for the interpreter to use internally by modifying the struct thing definition in thing.h. It must keep track of sign, whole, and fractional parts, but you are free to choose the internal representation. You can use a binary representation like those in ints, or you can simply store decimal digits (like a binary-coded decimal). Don't forget the principles of data abstraction! See the Suggestions section below.

(1) Implement arithmetic operations on bignum data (in C)

You will also have to implement arithmetic operations for the bignum data. These will be functions like bignum bignum_add(bignum a, bignum b), but should include addition, subtraction, multiplication, and exponentiation via non-negative integer powers. They will be called by the microscheme interpreter to manipulate bignums. You will add these new functions in microscheme.c.

(2) Modify microscheme I/O

You will have to modify the microscheme interpreter input/output to parse decimal number strings into bignums and print the bignums. The decimal strings can now be arbitrary-sized, meaning don't have a hardcoded limit to the number of characters in a number (hint hint). The code provided only supports input up to 30 characters and has a limited printing system for numbers.

Numbers should be parsed from and printed to the decimal form [-]Y.Z, where Y and Z are decimal digit strings of any length and [-] indicates an optional '-'. If Z has zero length then the decimal point should not be printed, but if Y has zero length a "0" should be printed in its place. For details on valid numbers at the input and the output, see below:

A valid number for input is defined as follows:

A valid number for output is defined as follows:
See the sample interactions for more examples.

(3) Add interpreter support for the new arithmetic ops

Even a Scheme interpreter that can handle bignums is boring if you can't perform any arithmetic operations on them! Add interpreter support for the new arithmetic operations, specifically + (addition), - (subtraction), * (multiplication), and expt (exponentiation to non-negative integer powers). Most of the arithmetic operations will have to be added, since only + is currently supported. This is the section where the interpreter learns to use your bignum arithmetic functions.

Your arithmetic operators should act similarly to the STk ones. That is, they should take arbitrary-sized arguments, where each argument can be any valid number, except for expt, which takes only two arguments, the second of which must be a non-negative integer. If + or * is called with no arguments, 0 or 1 should be returned, respectively. If - is called with one argument, the argument should be negated. Multi-argument calls to - should behave similarly to STk, namely all but the first argument should subtract from the first. Add these functions into microscheme.c.

The car and cdr operations are already implemented in the microscheme interpreter, and you should stick to the "Thing" data abstraction for evaluated Scheme expressions.

(4) Misc. Requirements


Handling Error Cases

We will only test your code on valid Scheme expressions. However, expressoins like (+ 'hi 'matt) are legal Scheme from a syntax perspective, and such things shouldn't crash microscheme. Instead, print some error and continue the read-eval-print loop. It doesn't matter what you print on such erroneous input, but make your errors descriptive like "non-numeric argument to +". Never segfault from any input!


Getting Started

Copy the contents of ~cs61c/proj/01 to your home directory.

   $ mkdir ~/proj
   $ cp -r ~cs61c/proj/01 ~/proj1
The directory contains several files: You can also add any of your own .c or .h files as appropriate. However, you must keep microscheme.h and core.c.

There is also a Makefile, which is a file that will tell us how to compile your submission (see the Compiling section). The directory also contains several text files of sample interactions with a bignum-enabled microscheme interpreter.

Try compiling and running the interpreter (see next section). Try a few commands and notice the definite lack of bignum support! Also, poke around the C file and figure out the read-eval-print process.


Compiling

Typing the following commands will compile and run your project:
  % gmake proj1
  % ./microscheme
gmake is a program that makes the compiling process easier when you have many files. It knows how to compile your submission based on the contents of the Makefile in the same directory.

Any additional files that needs to be compiled with your project will need to be added to the Makefile. Directions on augmenting the Makefile can be found in the Makefile itself. We will be using the Makefile to compile your assignment in the autograder, so be sure to update it.


Submitting

Submit your project using submit proj1 command from your proj1 folder. The submission process will ask you to submit the Makefile, microscheme.c, thing.h, any additional .c and .h (C header files) needed to compile your project, and a README, information you want to tell the reader about how your code is organized as well as what does and doesn't work. Please do not submit addtional files such as your executable, the code we already gave you, and the sample files we gave you. To reduce the number of files that submit will go through, it will help to run the command "gmake clean".


Correctness, Testing, and Examples

A completed and correct microscheme interpreter should act just like STk for the arithmetic operations described above, except it must also support big fractions like 0.99999999999999999999999999999. It should also produce the same output as the examples.

There are sample interactions in the project directory. You can use these interactions to test your own code, though you should do other testing as well. You can run commands like the following to compare your output to the examples:
   $ cat example0.in | ./microscheme > example0.myout
   $ diff example0.myout example0.out
UPDATE: You should test numbers with many digits using this pipe technique, as the Solaris machines have a fixed-length buffer for typed terminal inputs.

The examples given are not meant to be as exhaustive as the autograder will be, so matching these examples doesn't guarantee a perfect grade!

Here are some transcript examples:

Addition

   > (+ 1 2)
   3
   > (+ 0 0.0)
   0
   > (+ 1.0 5)
   6
   > (+ 000200.0000 050)
   250
   > (+ 99999999999999999999 99999999999999999999)
   199999999999999999998
   > (+ -100 100)
   0
   > (+ 123980234983 -2)
   123980234981
   > (+ -49283 200.999999999999999999999999999 300)
   -48782.000000000000000000000000001
   > (+ -50000000000 -50000000000 -50000000000 -50000000000)
   -200000000000

Subtraction

   > (- 10 5)
   5
   > (- 10000)
   -10000
   > (- 20 5 5 5 5)
   0
   > (- 0)
   0
   > (- 2390840928309.12093894892389 90082389.9999999999999999999999999999)
   2390750845919.1209389489238900000000000001
   > (- -500 23895)
   -24395

Multiplication

   > (* 100 0)
   0
   > (* 1 .99999999999999999999)
   0.99999999999999999999
   > (* 1000000 1000000)
   1000000000000
   > (* 1 -1 1 -1 1 -1 1 -1)
   1
   > (* 2.5 2 2)
   10
   > (* -6 5 2.25)
   -67.5

Exponentiation

   > (expt 238 0)
   1
   > (expt -1 100)
   1
   > (expt 111.2 5)
   17002936630.96832

Mixed (with nested expressions)

   > (+ (- 1 1) 10)
   10
   > (+ (- 452 4.52) (* 42 52) (- (+ 3 (- 4 2)) 4.24))
   2632.24
   > (* (- 1) (+ 100 50 2) (expt 2 4))
   -2432


Suggestions/Tips


Extra for Experts (and for extra credit!)

This section is completely optional, but if you do it you can gain a maximum of 10% extra credit on the project (so a perfect score with full extra credit is worth 110% of the project grade).

After making a backup copy of your primary proj1 submission, do the following:

  1. Add the division operator / for bignum division, which should work like STk's division but with our new bignums. Some results do not terminate (e.g. (/ 1 3)). For these cases it's up to you what to do (looping forever is fine); we will not test with any numbers that have infinite decimal (or other radix) representations.

  2. Using your division and multiplication routines, implement base conversion operations for bignums so that your interpreter can input and output in our three favorite bases: decimal, binary, and hexadecimal. You will have to edit microscheme's I/O so it can recognize the base identifiers "0b" and "0x" at the input and print them at the output when appropriate.
       > (base 2 10)
       0b1010
       > (base 10 0b100000)
       32
       > (base 0b1010 0b100000)
       32
       > (base 16 0b100000)
       0x20
       > (base 0x10 0b100000)
       0x20
       > (base 10 0b1000000000000000000000000000000000000000000000000000000000000)
       1152921504606846976
       > (base 2 70643625237641)
       0b10000000100000000000000001100000001110010001001
       > (base 2 -10.5)
       -0b1010.1
       > (base 10 -0b1010.1)
       -10.5
    
In summary, numbers can now appear in one of three forms: 0b_______, 0x_______, or ordinary decimal. They should still be bignums! Convert between them using the (base x y) where x must be 2, 10, or 16.

To submit the extra credit, you will submit it to "submit proj1_xc". Do NOT submit the extra credit version using "submit proj1" unless you are confident your changes will not effect the primary requirements of this project. Note, unless you submit using "submit proj1_xc", we will not know whether you tried the extra credit or not.