CS 304, Fall 2015
Lab Assignment L1:
Manipulating Bits
Assigned: 9/3, 2015
Due: 9/19, 11:00 AM
The purpose of this assignment is to become more familiar with bit-level representations and manipulations. You’ll do this by solving a series of programming “puzzles.” Many of these puzzles are artificial, but you’ll find yourself thinking much more about bits in working your way through them.
There are no “programming groups” for this (or any other) assignment. Each person in the class works alone and submits the work for the lab electronically. Any clarifications and revisions to the assignment will be posted on the course Web page.
Start by copying datalab-handout.tar from for this lab to a (protected) directory in which you plan to do your work. See the following commands. If you use your web browser to download the project into your home directory, then copy the downloaded file to your newly created directory.
will create a protected directory and move your source into this directory. Then give the command:
tar xvf datalab-handout.tar
Looking at the file bits.c you’ll notice a C structure student into which you should insert the requested identifying information about you (your name and login id). The btest test program will not run until you make this change.
The bits.c file also contains a skeleton for each of the 17 programming puzzles. Your assignment is to complete each function skeleton, except the last one, using only straightline code (i.e., no loops or conditionals) and a limited number of C arithmetic and logical operators. Specifically, you are only allowed to use the following eight operators (see operators http://icecube.wisc.edu/~dglo/c_class/operators.html):
operator | meaning |
|
|
! | logical NOT |
~ | bitwise NOT |
& | bitwise AND |
^ | bitwise XOR |
| | bitwise OR |
+ | addition |
<< | left shift |
>> | right shift |
|
|
|
A few of the functions further restrict this
list, and there are several other general restrictions on the
assignment. See the comments in bits.c for detailed rules and a discussion of
the desired coding style. Solving these 17 puzzles
will require fluent familiarity with the seven operators given in
the table above. You would be well-advised to read carefully the corresponding
operations in operators
http://icecube.wisc.edu/~dglo/c_class/operators.html
A very useful tip for this project: The right
shift operation (>>) Right shifting an unsigned
quatity always fills vacated bits with zero (logical shift). Right
shifting a signed quantity will fill with sign bits ("arithmetic
shift") on our department machine. For example, the following will
print out:
x>>31=0xffffffff,
y>>31=0x1, 0x80000000>>31=0x1
a>>31=0x0, b>>31=0x0, 0x70000000>>31=0x0
#include
<stdio.h>
int main() {
int x = 0x80000000,
a=0x70000000;
unsigned int y = 0x80000000,
b=0x70000000;
printf("x>>31=0x%x,
y>>31=0x%x, 0x80000000>>31=0x%x\n",
x>>31,y>>31, 0x80000000>>31);
printf("a>>31=0x%x,
b>>31=0x%x, 0x70000000>>31=0x%x\n",
a>>31,b>>31, 0x70000000>>31);
return 0;
}
Your code will be compiled with gcc and run and tested on one of the departmental Linux workstations. Your score will be computed out of a maximum of 84 points based on the following distribution:
The 17 puzzles you must solve have been given a difficulty rating between 1 and 4, such that their weighted sum totals to 44. We will evaluate your functions using the test arguments in btest.c. You will get full credit for a puzzle if it passes all of the tests performed by btest.c, half credit if it fails one test, and no credit otherwise.
Regarding performance, the main concern at this point in the course is that you can get the right answer. However, we want to instill in you a sense of keeping things as short and simple as you can. Furthermore, some of the puzzles can be solved by brute force, but we want you to be more clever. Thus, for each function there is a maximum number of operators that you are allowed to use to implement that function. This limit is very generous and is designed only to catch egregiously inefficient solutions. You will receive two points for each function that satisfies the operator limit.
Finally, 6 points are reserved for a subjective evaluation of the style of your solutions and your commenting. Your solutions should be as clean and straightforward as possible. Your comments should be informative, but they need not be extensive. Any function you attempt to solve that has no comments will be penalized.
|
|
|
|
Name | Description | Rating | Max Ops |
|
|
|
|
bitAnd(x,y) | Return (x&y) using only ~ and | | 1 | 8 |
copyLSB() | Return word with all bits set to | 2 | 5 |
|
the least significant bit of x | |
|
getByte(x,n) | Extract byte n from word x | 2 | 6 |
isEqual(x,y) | Return 1 if x == y and 0 otherwise | 2 | 5 |
bitMask(lowbit,highbit) | Return a mask consisting of 1’s between | 3 | 16 |
|
lowbit and highbit and zeros elsewhere | |
|
reverseBytes(x) | Reverse the bytes of x | 3 | 25 |
bang(x) | Compute !x without using ! | 4 | 12 |
leastBitPos(x) | Return a mask that marks the least | 4 | 6 |
|
significant 1-bit of x | |
|
|
|
|
|
|
Table 1 describes a set of functions that manipulate and test sets of bits. The “Rating” field gives the difficulty rating (the number of points) for the puzzle, and the “Max ops” field gives the maximum number of operators you are allowed to use to implement each function.
Function bitAnd computes the AND function. That is, when applied to arguments x and y, it returns (x&y). You may only use the operators | and ~.
Function copyLSB(x) returns the word with all bits set to the least significant bit of x. For example, the copyLSB(5) call should return 0xffffffff, and the copyLSB(6) call should return 0x00000000.
Function getByte(x,n) returns byte number n of x. Byte 0 is the least significant byte and byte 3 is the most significant byte. For example, the getByte(0x12345678,0) call should return 0x78, and the getByte(0x12345678,2) call should return 0x34.
Function isEqual(x,y) returns 1 if x == y and 0 otherwise.
Function bitMask(highbit,lowbit) returns a mask that is 1 between lowbit and highbit, inclusive, and is 0 elsewhere. You may assume that lowbit and highbit are both greater than or equal to 0 and less than or equal to 31. If lowbit > highbit, then the returned mask should be all zero. For example, the call bitMask(5,3) should return 0x00000038 (= 0..000111000) (the rightmost bit is bit number 0).
Function reverseBytes(x) returns an int whose bytes are in the reverse order of the bytes of x. For example, the reverseBytes(0x01020304) call should return 0x04030201.
Function bang(x) returns !x without the use of the ! operator. For example, bang(5) should return 0, and bang(0) should return 1.
Function leastBitPos(x) returns a mask marking the least significant 1-bit of x. For example, the call leastBitPos(0x60) should return 0x00000020 (note that 0x60 = 0110 0000 and that 0x20 = 0010 0000).
|
|
|
|
Name | Description | Rating | Max Ops |
|
|
|
|
minusOne(void) | Returns a value of -1 without using - | 1 | 2 |
tmax(void) | Return the maximum 32-bit two’s | 1 | 4 |
|
complement integer 0x7fff ffff | |
|
fitsBits(x,n) | Return 1 if x fits in an n-bit two’s complement int, | 2 | 15 |
|
otherwise, return 0 | |
|
addOK(x,y) | Return 1 if x+y can be computed | 3 | 20 |
|
without overflow, otherwise, return 0 | |
|
isGreater(x,y) | Returns 1 if x is greater than y; | 3 | 24 |
|
otherwise, returns 0. | |
|
IsNegative(x) | Return 1 if x < 0, otherwise return 0 | 3 | 6 |
multFiveEights(x) | Returns the product of x and 5/8, rounded toward 0 | 3 | 20 |
sm2tc(x) | Interprets x as a sign-magnitude integer | 4 | 15 |
|
and returns the value of x as a two’s complement integer | |
|
|
|
|
|
|
Table 2 describes a set of functions that make use of the two’s complement representation of integers.
Function minusOne returns -1 without using the “minus” operator.
Function tmax returns the largest positive 32-bit two’s complement int, namely 0x7fff ffff.
Function fitsBits(x,n) returns 1 if its leftmost parameter x can be represented as an n-bit two’s complement integer. You may assume that the number of bits n satisfies 1 ≤ n ≤ 32. For example, fitsBits(5,3) = 0, since 5 cannot be represented in 3 bits (the largest 3-bit positive number is 3), and fitsBits(-4,3) = 1, since -4 can be represented in 3 bits as 100 (it’s the 3-bit TMin).
Function addOK(x,y) returns 1 if the sum x+y can be computed without overflow; otherwise, it returns 0. For example,
addOK(0x80000000, 0x80000000) = 0
addOK(0x80000000, 0x70000000) = 1
Function isGreater(x,y) returns 1 if x > y, otherwise, it returns 0.
Function isNegative(x) returns 1 if x is negative; otherwise, it returns 0.
Function multFiveEights(x) returns the product of x and 5/8, rounded toward 0, without causing overflow. For example, multFiveEights(77) = 48, and multFiveEights(-22) = -13.
Function sm2tc(x)
interprets its argument x
as a sign-magnitude integer and returns the corresponding two’s
complement value. In the sign-magnitude number system, the
leftmost bit is the sign bit 1
for negative and 0 for
non-negative), and the remaining 31 bits give the magnitude (or
absolute value) of the integer. The following table gives several
sign-magnitude examples:
decimal | hex sign magnitude |
|
|
10 | 0x0000 000a |
-10 | 0x8000 000a |
-0 | 0x8000 0000 |
0 | 0x0000 0000 |
|
For this part of the assignment, you will implement some common single-precision floating-point operations. In this section, you are allowed to use standard control structures (conditionals, loops), and you may use both int and unsigned data types, including arbitrary unsigned and integer constants. You may not use any unions, structs, or arrays. Most significantly, you may not use any floating point data types, operations, or constants. Instead, any floating-point operand will be passed to the function as having type unsigned, and any returned floating-point value will be of type unsigned. Your code should perform the bit manipulations that implement the specified floating point operations.
Table 3 describes a set of functions that operate on the bit-level representations of floating-point numbers. Refer to the comments in bits.c and the reference versions in tests.c for more information. Return bit-level equivalent of expression (float) x. Result is returned as unsigned int, but it is to be interpreted as the bit-level representation of a single-precision floating point values. For exmaple, float_i2f(1) should return 0x3f800000 because 0x3f800000 is the floating point representation of 1 = 1.0 * 20.
|
|
|
|
Name | Description | Rating | Max Ops |
|
|
|
|
float_i2f(x) | Compute (float) x | 4 | 30 |
|
|
|
|
|
The included program fshow helps you understand the structure of floating point numbers. To compile fshow, switch to the handout directory and type:
You can use fshow to see what an arbitrary pattern represents as a floating-point number:
You can also give fshow hexadecimal and floating point values, and it will decipher their bit structure.
This taget in the makefile first strips out the printf statements in your bits.c file, then invokes the submit program to submit your bits.c file. The submission can be repeated as many times as you wish. Only the most recently submitted version of your bits.c is retained.
Nothing is more frustrating that accidentally deleting a file that you have to hand in. To avoid this frustration, you should back up your source code regularly (such as, before you begin each editing session) with the command:
As you can see in the Makefile, the backup target creates a compressed tar file named
that contains the Makefile and the files of the directory containing the Makefile having .c and .h suffixes.
If you ever need to retrieve any of the backed up information, then type
This will create a subdirectory of the current
directory having the same name as the current directory where the
files from the tar file are placed. For example, if your working
directory is ./lab1, then the
tar fxv backup.tar
command will extract the backed up files into a subdirectory ./lab1/lab1. You
can recover the files that you want from this subdirectory.
----------------------
You should read the following hints:
You are expected to do your code development on one of the Linux workstations in M-S 121. Make sure that the version of bits.c that you turn in compiles and runs correctly on one of these machines.
If you are accustomed to programming in C++ or Java, then programming in C will require a small adjustment from you. Unlike Java or C++, all variables that you use in a procedure must be declared at the beginning of the procedure.
Your primary debugging tool will be the printf statement. For example, if you want to know the hexadecimal value of your int variable myvar, then insert the statement
where you want to check the variable’s value. The %08x format specifies 8-column hexadecimal output with leading zeros, if necessary (see K&R, p. 153 for more information).
A useful fact that you already know needs to be emphasized here, because you may be able to use it in several places:
For a variable int x, the expression x>>31 is 0xffffffff if x < 0 and 0x00000000 if x≥ 0.
The dlc program, a modified version of an ANSI C compiler, will be used to check your programs for compliance with the coding style rules. Type dlc -help to display all of its options. This help output will show you, for example, that you can use dlc to measure the operator counts of your functions by executing the command:
The dlc compiler balks at the #include <stdio.h> preprocessor statement, so do not include it, even though gcc will complain about printf being undeclared when you invoke make.
Check the file README for documentation on running the btest program. You’ll find it helpful to work through the functions one at a time, testing each one as you go. You can use the -f flag to instruct btest to test only a single function, e.g., ./btest -f addOK.
Be sure to read carefully the comments at the top of the bits.c file. Note in particular that you cannot use in your code any constant larger than 0xff.
---------------------------
Function bitMask(highbit,lowbit) returns a mask that is 1 between
lowbit and highbit, inclusive, and is 0 elsewhere. You may assume
that lowbit and highbit are both greater than or equal to 0 and
less than or equal to 31. If lowbit > highbit, then the
returned mask should be all zero. For example, the call
bitMask(5,3) should return 0x00000038 (= 0..000111000) (the
rightmost bit is bit number 0). You can see bit 3,4,5 are all 1's.
Hint: Some found it confusing. Give you another example:
bitMask(6,3) should return 0x00000078 (= 0..001111000). bit 0,1,2
are all 0, bit 3,4,5,6 are all 1's.
---------------------------
Function bang(x) returns !x without the use of the ! operator. For
example, bang(5) should return 0, and bang(0) should return 1.
Hint: compare 5 and -5, also compare 0 and -0. See any pattern you
can use?
---------------------------
Function leastBitPos(x) returns a mask marking the least
significant 1-bit of x. For example, the call leastBitPos(0x60)
should return 0x00000020 (note that 0x60 = 0110 0000 and that 0x20
= 0010 0000).
Hint: compare x and -x. try x=0b001101010000. See any pattern?
---------------------------
Function fitsBits(x,n) returns 1 if its leftmost parameter x can
be represented as an n-bit two’s complement integer. You may
assume that the number of bits n satisfies 1 ≤ n ≤ 32. For
example, fitsBits(5,3) = 0, since 5 cannot be represented in 3
bits (the largest 3-bit positive number is 3), and fitsBits(-4,3)
= 1, since -4 can be represented in 3 bits as 100 (it’s the 3-bit
TMin).
Hint: You don't really need to compare the biggest and smallest
n-bit numbers. Any number that can be represented in n bits has
some pattern if you use 32 bits to represent it.
---------------------------
Function addOK(x,y) returns 1 if the sum x+y can be computed
without overflow; otherwise, it returns 0.
Hint: We talked about this in class. Only the sign bits matter.
---------------------------
Function isGreater(x,y) returns 1 if x > y, otherwise, it
returns 0.
Hint: you have to consider the case for overflow.
---------------------------
Function multFiveEights(x) returns the product of x and 5/8,
rounded toward 0, without causing overflow. For example,
multFiveEights(77) = 48, and multFiveEights(-22) = -13.
Hint: You should be able to get it correct for x>=0. For
x<0, if 8 does not divide x, you need to adjust the quotient.
Better way is to adjust x before you take division when x<0.
---------------------------
For some problems in this lab, you may also want to
make sure to check x and x's negation -x.
-x=~x+1 This ~x+1 may give you a lot of useful information in
several problems. For example, bang(x), leastBitPos(x),
---------------------------
float_i2f(x) -- please use round to even.
See B&O book pp.110-112
The default rounding mode of IEEE floating-point is to use
round-to-even.
In then following, I will round off the last 7 bits. Those
examples will give you a good understanding about round-to-even.
A.
1.01010101010101010101011,1000001
This number is closer to
1.01010101010101010101100 than
1.01010101010101010101011
so we will use
1.01010101010101010101100
B.
1.01010101010101010101011,01000001
This number is closer to
1.01010101010101010101011 than
1.01010101010101010101100
so we will use
1.01010101010101010101011
C.
1.01010101010101010101011,10000000
This number is equal distance from
1.01010101010101010101011 and
1.01010101010101010101100
In round-to-even, we will round it to an even number, that is to:
1.01010101010101010101100
D.
1.01010101010101010101010,10000000
This number is equal distance from
1.01010101010101010101010 and
1.01010101010101010101011
In round-to-even, we will round it to an even number, that is to:
1.01010101010101010101010
---------------------------
Examples:
------------------------
/* * isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII
codes for characters '0' to '9')
* Example: isAsciiDigit(0x35) = 1.
* isAsciiDigit(0x3a) = 0.
* isAsciiDigit(0x05) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 3
*/
int isAsciiDigit(int x) {
int bias1 = ~0x2F;
int bias2 = 0x3a;
int lower = x + bias1;
int upper = ~x + bias2;
return !((lower|upper) >> 31);
}
-----------------------------
/*
* sign - return 1 if positive, 0 if zero, and -1 if negative
* Examples: sign(130) = 1
* sign(-23) = -1
* sign(0) = 0
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 10
* Rating: 2
*/ int sign(int x)
We can easily replicate sign bit by doing:
x>>31 = 0x0 if x>=0;
x>>31 = 0xffffffff=-1 if x<0
We are close now. We need to get 0x1 if x>0.
Think hard, we can do the following:
int sign(int x) {
return (x>>31) | (!!x);
}
-----------------------------------------------
/* * absVal - absolute value of x
* Example: absVal(-1) = 1.
* You may assume -TMax <= x <= TMax
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 10
* Rating: 4
*/ int absVal(int x)
----
The program should run as follows:
if (x>=0) return x;
else return -x;
we know -x=~x+1;
We need to come up with x if x>=0, ~x+1 if x<0. How do we do
this?
Well, let's make some observation. The only difference between a
negative int and a non-negative int is the sign bit. We can
replicate the sign bit by doing the following:
x>>31
This gives us 0XFFFFFFFF if x<0 and 0X00000000 if x>=0.
Then (x>>31) ^ x gives us x if x>=0, and ~x if x<0.
We are close now. We only need to come up with a 1 for x<0. The
following generates a 1 for x<0
(x>>31) & 0x1 gives us 1 if x<0 and 0 if x>=0.
Then the program is like this:
int absVal(int x)
{ int mask=x>>31;
return (mask ^ x) + (mask & 1);
}
----------------------