Release Date: | Wednesday, January 31. |
Acceptance Deadline: | Sunday, February 4, 11:59 pm. |
Checkpoint 1: | Tuesday, February 6, 11:59 pm. At least four puzzles should be completed (including documentation). |
Checkpoint 2: | Sunday, Feburary 11, 11:59 pm. At least nine puzzles should be completed (including documentation). |
Due Date: | Thursday, February 15, 11:59 pm. |
Collaboration Policy: | Level 1 |
Group Policy: | Individual |
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:
int
type! ~ & ^ | + << >>
Individual puzzles may further restrict 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.pl
: AutoscoringThe driver.pl
utility (a Perl 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.pl
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.
nano
, there are two ways to do so. First, you can launch the editor with the -l
option, e.g., nano -l puzzles.c
. A second option that avoids having to remember to pass the -l
flag is to create a file in your home directory called .nanorc
(which is a nano
configuration file) and then write the following line in it:
set linenumbersYou can create your
.nanorc
file like the following:
nano ~/.nanorcOnce you've saved the line above into this file, all subsequent invocations of
nano
will automatically have line numbers turned on. If you're using an editor other than nano
, consult Google for instructions on enabling line numbers.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.pl
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.blc
compiler will always output the following warning, which you can safely ignore:
Non-includable file <command-line> included from includable file /usr/include/stdc-predef.h.
<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).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 braces), all variable declarations must come before all other statements. For example, the following code will not pass blc
, even though it is valid C code:
int foo(int x) { int a = x; // a declaration a *= 2; // not a declaration int b = a + 3; // ERROR: can't put a declaration after a non-declaration return b; }To fix the above code so that it passes
blc
, you would need to break the int b = a + 3;
line into separate declaration and assignment lines, as in the following:
int foo(int x) { int a = x; // a declaration int b; // another declaration a *= 2; // not a declaration b = a + 3; // not a declaration return b; }
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 initialize and clone your private lab repository from GitHub, which will contain the lab files. 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 hopper
.
Your last-pushed puzzles.c
at the lab due date (and each of the intermediate checkpoints) will constitute your lab and checkpoint submissions. Remember that if you haven't committed and pushed your work, it's not submitted. Committing and pushing frequently as a way to save your work is a good idea as well.
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).
Just for fun, as part of this lab, 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 awarded the Bit Hacker designation and associated 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. 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 link below to view the current scoreboard (only active for the duration of the lab).