Project 1 - A Bit Puzzling

This project should be completed individually.

This project is designed to make you more familiar with the bit-level representations of data used by computers. At the same time, you will become well-acquainted with the bitwise manipulations available in many languages (like C). You will do this by solving a series of (fun, enlightening, and challenging!) programming puzzles in C.

Start by reading through the entire project description, particularly the advice section!

Project Dates

Assigned

Sunday, February 5.

Checkpoint 1

Friday, February 10. At least three puzzles must be completed by this checkpoint (including documentation).

Checkpoint 2

Wednesday, February 15. At least nine puzzles must be completed by this checkpoint (including documentation).

Due

Monday, February 20. All fifteen puzzles are due by this date.

Project Overview

In this project, you will modify a single C source file, puzzles.c, which is included in the starter files. This file contains the skeleton for a set of 15 programming puzzles. Each puzzle is a function which should compute a simple operation (e.g., returning whether a given number is positive or negative). Your task is to complete each function using a highly restricted subset of the full C language. In particular:

You may not use any branching or looping constructs (e.g., conditionals or loops) - only straight-line code.

You may not use macros, casting, function calls, or any data type other than ints. Don't worry if some of those terms don't mean anything to you because you haven't used C before.

You are restricted to only the following set of operators: ! ~ & ^ | + << >>In some cases, operators may be further restricted, and will be noted in puzzles.c.

You may only use constants from 0 to 255 (0xFF).

Each function must be completed using only a certain number of operators (or fewer). This restriction is primarily to prevent 'brute-force' type solutions to some of the puzzles.

Exception: For floating-point puzzles, you do not need to adhere to the restrictions on branching, looping, constants, or operators, and you may use both int and unsigned types (but no floating-point types). Other restrictions still apply (e.g., no function calls).

In order to solve the puzzles while following the rules above, you will need to have a clear understanding of data representations and be clever about using bitwise operators!

Puzzle Information

Puzzles may make use of either raw bitwise values (i.e., bits not interpreted as numbers), integers, or floating-point numbers. Integers use a 2's complement representation and floating point numbers use the standard IEEE floating point representation discussed in class.

Each puzzle is assigned a difficulty rating from 1 to 4 reflecting my subjective rating of the complexity of that puzzle (1 = easiest, 4 = hardest). Puzzles with the lowest rating may be straightforward given a few minutes of thought. Harder puzzles will almost certainly require a non-trivial amount of thought, experimentation, and possibly pen-and-paper exploration. Don't underestimate how long the later puzzles will take based on the earlier puzzles!

In addition to your code implementing each puzzle, you must provide explanations (via comments embedded in puzzles.c) of how and why each of your solutions works. Your comments must go beyond a simple translation of the bitwise operations you used. Rather, your comments must clearly demonstrate that you understand why (as opposed to simply how) your solution works. For example, a comment that says "shifts the bits right by 3 and then AND's by 1" simply restates the binary operations themselves and does not provide any real information. A better comment might be "extracts the fourth-lowest bit of the number by shifting right 3 and then ANDing by 1".

In some cases, your comments may end up exceeding the length of your code (though this is not strictly a requirement). Do not neglect your explanations, as they will factor significantly into your project grade.

Complete reference implementations of the puzzles are provided in tests.c that make use of the entire C language. Refer to these if you have any question about how a given function is supposed to behave. However, given that these implementations do not have any restrictions, they are not likely to be especially helpful in how to actually approach your functions.

Testing

Checking your puzzle solutions can be tricky. Luckily, you will have a couple of useful tools at your disposal.

ishow: Integer Representations

The ishow utility accepts an integer input (either in decimal or hex format) and outputs its signed, unsigned, and raw-bit (in hex) equivalents. For example:

fshow: Floating-Point Representations

The fshow utility accepts either an integer or hex value representing an arbitrary bit pattern, or a float-point number, and outputs the corresponding floating point value and representation components. For example:

blc: Compliance

The blc (bit lab compiler) utility is a modified C compiler that will check your solutions for compliance with the coding rules specified. To check the compliance (but not correctness) of your solutions, run blc on your puzzles.c file as follows:

$ ./blc puzzles.c

If your program is fully compliant, no output will be produced (otherwise, an error message will be printed). You can also count the number of operations in each function by passing the -e switch:

$ ./blc -e puzzles.c

btest: Correctness

To check the correctness (but not compliance) of your solutions, use the btest utility. To use btest, you must compile it using the included Makefile by typing make, which will compile it using your current puzzles.c file. This means that your must rebuild btest each time you modify puzzles.c. You should not modify the included Makefile.

Once compiled, simply run the utility directly to check the correctness of all your solutions:

$ ./btest

You can also use the -f flag to tell btest to test only a single named function, like this (assuming a function named foo):

$ ./btest -f foo

While btest normally checks your solutions against many possible inputs, you can also check specific inputs using the flags -1, -2, and -3. For example, to test the correctness of foo(5, 0xF), you could run the following:

$ ./btest -f foo -1 5 -2 0xF

driver.pl: Autoscoring

The driver.pl utility (a Perl script, if you are curious) will both run btest to check your solutions as well as count the number of operations used. To use it, simply execute it directly:

$ ./driver.pl

The script will output an autogenerated score for each puzzle out of 1-4 possible correctness points (based on the difficulty rating of the puzzle) and 2 possible performance points (based on the number of operations used). The total score shown does not correspond directly to your project score (in particular, a full performance score is not necessary for full credit) but will give you a sense of where improvement is possible.

General Advice (read this carefully!)

Start early! Some of these puzzles are quite tricky, and you should not expect to solve these problems in one sitting.

Although you are technically submitting a program here, don't approach these puzzles like a 'programming project'. Sitting down to write code right off the bat is a very bad way to approach these problems. Instead, think about the puzzles and try things out with pen and paper before you start typing any C code. In most cases, the amount of actual typing required for a correct solution is minimal.

Remember that all individual collaboration rules (as given on the course home page) apply to this assignment. For example, you may verbally discuss approaches at a high level with other students, but you may not step another student through a puzzle solution. Please ask if you have any questions about what is or isn't allowed!

Debugging Tips

The blc compiler will always output the following warning, which you can safely ignore:

Don’t include the <stdio.h> header file in your puzzles.c file, as it confuses blc and will result in non-intuitive error messages. You can still use printf for debugging without including the <stdio.h> header, although gcc will print a warning that you can ignore. Here's a quick tutorial on printf if you haven't used it before (usage in C is basically identical to the Perl examples shown there).

The blc program enforces a stricter form of C declarations than normally enforced by gcc. In particular, within a given block of code (i.e., as delimited by a set of curly prices), all variable declarations must come before all other statements. For example, the following code will not pass blc:

To fix the above code, you would need to break the int b = a; line into separate declaration and assignment lines, and put the declaration before the a *= 2; line.

Shifting by 32 bits or more has undefined behavior. This means that if your program shifts a value by 32 or more bits, you can't count on any particular behavior -- not even the behavior you observe in any particular instance. We needn't worry about the reasons for this for now (though it has to do with the compiler performing some optimization tricks), but suffice to say that if you try to shift by 32 or more bits, you will likely introduce very subtle bugs in your program. Therefore, don't.

Logistics

The project files have been added to your SVN directory for you -- to download them, simply do an update in your checked-out SVN directory. The *only* file you need to modify is puzzles.c.

Remember that you need to do your work on turing, not on your local machine -- since the files are compiled for Linux, they will not execute on a Mac or a Windows PC.

Your committed puzzles.c at the project due date (and each of the intermediate checkpoints) will constitute your project submission. Remember that if you haven't committed your work, it's not submitted (but you should be committing frequently as you work in any case)!

Evaluation

You will be evaluated on the contents of your puzzles.c file, which must contain both your puzzle solutions and your solution explanations. Do not store any work that you would like to be considered outside this file!

Your project will be evaluated as follows:

Correctness: 50%

Performance (number of operations): 20%

Documentation (comments): 30%

Partial credit is possible for puzzle solutions that are not complete, provided some understanding of the problem is apparent (note that good documentation will be doubly important here).

"Bit Hacker" Contest

Just for fun, as part of this project, you may participate in a completely optional contest to compete against other students and my solution to develop the most efficient puzzle solutions. The goal of the contest is to solve each puzzle using the fewest number of operators. Students who match or beat my solution's operator count for each puzzle are winners (prizes include the "Bit Hacker" designation and bragging rights!)

To submit your puzzle entries to the contest, simply call the driver with a -u flag providing a nickname that you would like to show on the scoreboard:

$ ./driver.pl -u "Your Nickname"

You may use any nickname you like and it need not identify yourself to other contest entrants (though I will be able to see your entries regardless of your nickname). You can submit to the contest as often as you like and there is never any penalty for doing so.

Your most recent contest submission will appear on a real-time scoreboard, identified only by your nickname. Follow the below link to view the current scoreboard.