This lab is designed to familiarize you with the bit-level representations of data used by computers, as well as the bitwise manipulations supported by our machines. Your task is to solve a series of puzzles implemented as short C functions in which various operations are implemented using only bitwise operators and no higher-level constructs. To do so, you will need a clear understanding of the integer and floating point representations used by modern machines as well as comfort working with the various bitwise operators.
In this lab, 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, all of the following are prohibited in your functions, unless stated otherwise:
int type! ~ & ^ | + << >>Individual puzzles may further restrict or expand your allowed operations, which will be noted in the puzzle description. Note that you are allowed to use assignment (=) to create variables in your functions (these will be helpful largely for the sake of readability). For example, you might write a line that assigns a subexpression to a variable, and then reuse that variable in a subsequent expression on another line. Use of variables will help prevent writing very long expressions on a single line.
Beyond the basic coding rules listed above, each function must be completed using using no more than a specified number of operators. This restriction is primarily to prevent 'brute-force' type solutions to some of the puzzles. Your goal should be to complete each puzzle using as few operators as possible. All operators except for = (assignment) count towards your total.
Floating-point puzzles: For floating point puzzles, the set of restrictions is somewhat relaxed. For these puzzles, you are allowed to use conditionals, loops, any constants, most operators, and both the int and unsigned types. You still cannot use any floating-point types, casting, functions, or macros.
Puzzles may make use of either raw bitwise values (i.e., bits not interpreted as numbers), integers, or floating-point numbers. Integers use a two's complement representation and floating point numbers use the IEEE 754 floating point representation discussed (or soon to be discussed) in class.
Each puzzle is assigned a difficulty rating from 1 to 3 reflecting their relative complexity (1 = easiest, 3 = hardest). Rating 1 puzzles may be reasonably doable in only a few minutes. Harder puzzles (especially rating 3 puzzles) will almost certainly require a significant amount of thought, experimentation, and pen-and-paper exploration. Don't underestimate how long later puzzles will take based on earlier puzzles!
In addition to your code implementing each puzzle, you must provide explanations (via comments embedded in puzzles.c) of how your solutions are working. Your comments should go beyond a simple code-to-English translation of the bitwise operations you used and should 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 and does not add any further information. A better comment might be "extracts the fourth-lowest bit". Your comments may be written either inline within your functions or in the function headers (or a mix of both). Do not neglect your explanations, as they will factor significantly into your lab 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 write your functions.
Summaries of the 15 puzzles are given below. The first 13 puzzles deal with integer representations, while the last 2 puzzles use floating point representations. Complete details of each puzzle are given in the puzzles.c file.
| Puzzle Name | Rating | Description | |
|---|---|---|---|
maxVal() | 1 | return largest 2's complement value | |
negCheck(x) | 1 | return whether x < 0 | |
lsbCopy(x) | 1 | set all bits of result to least significant bit of x | |
andBits(x,y) | 1 | return x&y using only ~ and | | |
xorBits(x,y) | 1 | return x^y using only ~ and & | |
setThirdBits() | 2 | return value with every 3rd bit set to 1 | |
byteExtract(x,n) | 2 | extract byte n from x | |
byteSwitch(x,n,m) | 2 | swap bytes n and m of x | |
addOverflow(x,y) | 2 | determine if x+y causes overflow | |
bitFit(x,n) | 3 | return whether x can be represented using n bits | |
shiftLogical(x,n) | 3 | return x>>n using a logical shift | |
not(x) | 3 | return !x without using ! | |
signMagnitude(x) | 3 | convert from 2's complement to sign-magnitude | |
fp_abs(x) | 2 | (floating point) return absolute value of x | |
fp_twice(x) | 3 | (floating point) return 2*x |
Checking your puzzle solutions can be tricky. Luckily, you will have a couple of useful tools at your disposal, described below.
ishow: Integer RepresentationsThe 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:
$ ./ishow 20 Hex = 0x00000014, Signed = 20, Unsigned = 20 $ ./ishow 0xffffffff Hex = 0xffffffff, Signed = -1, Unsigned = 4294967295
fshow: Floating-Point RepresentationsThe 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:
$ ./fshow 5.25 Floating point value 5.25 Bit Representation 0x40a80000, sign = 0, exponent = 0x81, fraction = 0x280000 Normalized. +1.3125000000 X 2^(2) $ ./fshow 100 Floating point value 1.401298464e-43 Bit Representation 0x00000064, sign = 0, exponent = 0x00, fraction = 0x000064 Denormalized. +0.0000119209 X 2^(-126)
blc: ComplianceThe 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: CorrectnessTo 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 you 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.py: AutoscoringThe driver.py utility (a Python script) will verify your solutions' compliance using blc and correctness using btest, as well as count the number of operations used. To use it, simply execute it directly:
$ ./driver.py
The script will output an autogenerated score for each puzzle out of 1-3 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 lab score (in particular, a full performance score is not necessary for full credit) but will give you a sense of where improvement is possible, as well as flagging any noncompliant or nonfunctional puzzle solutions.
btest says that your puzzle solution works, that does not necessarily mean that your solution is valid, because btest does not check for compliance with the coding rules. To verify compliance, you must use blc as well; or, more easily, just run the driver.py utility, which will run both blc and btest automatically. The primary utility of running btest directly is that it will tell you an input that causes your solution to fail, but don't make the mistake of relying on btest exclusively.if and while.printf for debugging without including the <stdio.h> header, although blc will correctly flag any printf function call (or any other function call) as an illegal function call, so remove all printf debugging before submitting your assignment. 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).andBits and xorBits puzzles. These laws are summarized as follows, relating two sets (or boolean values) X and Y:btest. Therefore, make sure this can't happen.Follow the same steps you followed in Lab 0 to find your Lab 1 assignment. The only file you will modify is puzzles.c.
Remember that since the files are compiled for Linux (and will not execute on a Mac or a Windows PC), you will need to do your work on krebs.
Although you may submit other files, only your last-submitted puzzles.c at the lab due date will be graded.
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 program will be evaluated on the correctness of your puzzle solutions, the number of operations used to construct your solutions, and your puzzle explanations (i.e., documentation).
Partial credit is possible for puzzle solutions that are not complete, provided some understanding of the problem is apparent (note that good documentation is doubly important here to demonstrate your understanding).
Can you make your submission more efficient than the instructor's? My solutions (without doing anything overly clever) require a combined 93 total operators, but it is possible to do a lot better! I will assign extra credit to the top student submissions using the fewest number of total operators. Remember: use ./driver.py to view your current total operator count!