/*
* CS:APP Data Lab
*
*
*
* bits.c - Source file with your solutions to the Lab.
* This is the file you will hand in to your instructor.
*
* WARNING: Do not include the header; it confuses the dlc
* compiler. You can still use printf for debugging without including
* , although you might get a compiler warning. In general,
* it's not good practice to ignore compiler warnings, but in this
* case it's OK.
*/
#if 0
/*
* Instructions to Students:
*
* STEP 1: Read the following instructions carefully.
*/
You will provide your solution to the Data Lab by
editing the collection of functions in this source file.
INTEGER CODING RULES:
Replace the "return" statement in each function with one
or more lines of C code that implements the function. Your code
must conform to the following style:
int Funct(arg1, arg2, ...) {
/* brief description of how your implementation works */
int var1 = Expr1;
...
int varM = ExprM;
varJ = ExprJ;
...
varN = ExprN;
return ExprR;
}
Each "Expr" is an expression using ONLY the following:
1. Integer constants 0 through 255 (0xFF), inclusive. You are
not allowed to use big constants such as 0xffffffff.
2. Function arguments and local variables (no global variables).
3. Unary integer operations ! ~
4. Binary integer operations & ^ | + << >>
Some of the problems restrict the set of allowed operators even further.
Each "Expr" may consist of multiple operators. You are not restricted to
one operator per line.
You are expressly forbidden to:
1. Use any control constructs such as if, do, while, for, switch, etc.
2. Define or use any macros.
3. Define any additional functions in this file.
4. Call any functions.
5. Use any other operations, such as &&, ||, -, or ?:
6. Use any form of casting.
7. Use any data type other than int. This implies that you
cannot use arrays, structs, or unions.
You may assume that your machine:
1. Uses 2s complement, 32-bit representations of integers.
2. Performs right shifts arithmetically.
3. Has unpredictable behavior when shifting an integer by more
than the word size.
EXAMPLES OF ACCEPTABLE CODING STYLE:
/*
* pow2plus1 - returns 2^x + 1, where 0 <= x <= 31
*/
int pow2plus1(int x) {
/* exploit ability of shifts to compute powers of 2 */
return (1 << x) + 1;
}
/*
* pow2plus4 - returns 2^x + 4, where 0 <= x <= 31
*/
int pow2plus4(int x) {
/* exploit ability of shifts to compute powers of 2 */
int result = (1 << x);
result += 4;
return result;
}
FLOATING POINT CODING RULES
For the problems that require you to implent floating-point operations,
the coding rules are less strict. You are allowed to use looping and
conditional control. You are allowed to use both ints and unsigneds.
You can use arbitrary integer and unsigned constants.
You are expressly forbidden to:
1. Define or use any macros.
2. Define any additional functions in this file.
3. Call any functions.
4. Use any form of casting.
5. Use any data type other than int or unsigned. This means that you
cannot use arrays, structs, or unions.
6. Use any floating point data types, operations, or constants.
NOTES:
1. Use the dlc (data lab checker) compiler (described in the handout) to
check the legality of your solutions.
2. Each function has a maximum number of operators (! ~ & ^ | + << >>)
that you are allowed to use for your implementation of the function.
The max operator count is checked by dlc. Note that '=' is not
counted; you may use as many of these as you want without penalty.
3. Use the btest test harness to check your functions for correctness.
4. Use the BDD checker to formally verify your functions
5. The maximum number of ops for each function is given in the
header comment for each function. If there are any inconsistencies
between the maximum ops in the writeup and in this file, consider
this file the authoritative source.
/*
* STEP 2: Modify the following functions according the coding rules.
*
* IMPORTANT. TO AVOID GRADING SURPRISES:
* 1. Use the dlc compiler to check that your solutions conform
* to the coding rules.
* 2. Use the BDD checker to formally verify that your solutions produce
* the correct answers.
*/
/*
* bitAnd - x&y using only ~ and |
* Example: bitAnd(6, 5) = 4
* Legal ops: ~ |
* Max ops: 8
* Rating: 1
*/
int bitAnd(int x, int y) {
return ~(~x | ~y);
}
布尔代数基本定理:a & b = ~(~a | ~b)
/*
* getByte - Extract byte n from word x
* Bytes numbered from 0 (LSB) to 3 (MSB)
* Examples: getByte(0x12345678,1) = 0x56
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 2
*/
int getByte(int x, int n) {
return (x >> (n << 3)) & 0xFF;
}
x 右移n个字节,mask下0xFF。8bit为一个字节,n个字节表示n*8 = n << 3。
/*
* logicalShift - shift x to the right by n, using a logical shift
* Can assume that 0 <= n <= 31
* Examples: logicalShift(0x87654321,4) = 0x08765432
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int logicalShift(int x, int n) {
int aritShift = x >> n;
int mask = (~(1 << 31) >> n << 1) + 1;
return aritShift & mask;
}
先进行算术右移,将左边因为算术右移产生的1 mask掉。其中mask = 00…011…1,前面n个0,后面全为1。mask = (~(1 << 31) >> n << 1) + 1. 注意⚠️最后+1,是因为左移1位产生的0,要给它填上。
/*
* bitCount - returns count of number of 1's in word
* Examples: bitCount(5) = 2, bitCount(7) = 3
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 40
* Rating: 4
*/
int bitCount(int x) {
int mask1;
int mask2;
int mask4;
int mask8;
int mask16;
mask1 = 0x55 | 0x55 << 8;
mask1 = mask1 | mask1 << 16;
mask2 = 0x33 | 0x33 << 8;
mask2 = mask2 | mask2 << 16;
mask4 = 0x0F | 0x0F << 8;
mask4 = mask4 | mask4 << 16;
mask8 = 0xFF | 0xFF << 16;
mask16 = 0xFF | 0xFF << 8;
x = (x & mask1) + ((x >> 1) & mask1);
x = (x & mask2) + ((x >> 2) & mask2);
x = (x + (x >> 4)) & mask4;
x = (x + (x >> 8)) & mask8;
x = (x + (x >> 16)) & mask16;
return x;
}
这题总体思路是divide and conquer。
将x的bit表示,分成两个一组,组内一位相加。
例如:10010010111010101011101000001101,分成 10 01 00 10 11 10 10 10 10 11 10 10 00 00 11 01。
组内相加得到x1 = 01 01 00 01 10 01 01 01 01 10 01 01 00 00 10 01. 这样表示每组分别有多少个1。
实现这个操作只要将(x & mask1) + ((x >> 1) & mask1), 其中mask1 = 01010101…0101.
mask1 可以经过如上代码得到。
再将x1的bit表示,分成四个一组,同样组内两位相加。
10 01 00 10 11 10 10 10 10 11 10 10 00 00 11 01,分成1001 0010 1110 1010 1011 1010 0000 1101。
组内相加得到x2 = 0010 0010 0101 100 0101 0100 0000 0101。同样地表示没组分别有多少个1。
实现这个操作只要将(x & mask2) + ((x >> 2) & mask2), 其中mask2 = 00110011…0011.
mask2 可以经过如上代码得到。
以此类推…
注意⚠️,最后三次操作没有先经过mask,而是直接相加再mask,不是因为遵守分配律,而是因为前面2次要先mask再相加是因为算术右移产生1,要先把它mask成0。 而经过第二次的四位一组,组内2位两位相加,表示bit 1的数量肯定不会超过4,即四位一组表示成0100。这样进行算术右移不会产生1,所以为了减少运算次数,可以先相加再mask。
/*
* bang - Compute !x without using !
* Examples: bang(3) = 0, bang(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int bang(int x) {
return (((~x + 1) | x) >> 31) + 1;
}
这里利用了补码相反数的特性。除了0,0x80000000以外的任何数,它与其相反数的符号为一定是不同的。而0x80000000的相反数仍然为本身,所以只有0 | 0的相反数的最高位为0,其余均为1。因此 ((~x + 1) | x ) >> 31 若不是0,那么会产生0xFFFFFFFF;若为0,则为0x0。经过 + 1之后,则实现了 !x 功能。
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
return 0x80 << 24;
}
水题,直接利用补码定义就好了。
/*
* fitsBits - return 1 if x can be represented as an
* n-bit, two's complement integer.
* 1 <= n <= 32
* Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int fitsBits(int x, int n) {
int shiftNumber = 32 + ~n + 1;
int tmp = x << shiftNumber >> shiftNumber;
return !(tmp ^ x);
}
x能否用更少的位数n表示,可以看成n位的数能否经过符号扩展,变成与x位数一样的数。因为符号扩展的位都是0或都是1。所以经过 x << (32 - n) >> (32 - n)之后,如果能用n位表示,则一定与原来x相同。判断一个数是否和另一个数相同用异或即可,相同则为0。最后加!,如果相同,则返回1,表示能用n位表示x。
/*
* divpwr2 - Compute x/(2^n), for 0 <= n <= 30
* Round toward zero
* Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int divpwr2(int x, int n) {
int signMask = x >> 31;
return (x + (signMask & ((1 << n) + ~0))) >> n;
}
分类讨论,如果x >= 0, 那么 x/(2^n) = x >> n;如果 x < 0, 那么x/(2^n) = (x + (1 << n) - 1) >> n; 现在构造一个数 bias 使得在 x >= 0时,bias = 0;在 x < 0 时,bias = (1 << n) - 1; signMask = x >> 31, 表示如果x负数则,signMask = 0xFFFFFFFF;否则signMask = 0x0。所以 bias = signMask & (1 << n) + ~0) 。其中~0 = -1。
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
return ~x + 1;
}
水题,按照补码相反数定义来就好了。
/*
* isPositive - return 1 if x > 0, return 0 otherwise
* Example: isPositive(-1) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 3
*/
int isPositive(int x) {
return !(!x | (x >> 31));
}
由于正数不包括0,所以不能只看符号位来判断。所以可以进行下转换正数 = !( x为 0 或 负数)。若x为0,!x = 1;若x为负数,可以看符号为(x >> 31) = 0xFFFFFFFF。因此如果x为0或负数,那么!x | (x >> 31) != 0。再加个逻辑反,!!(!x | (x >> 31) = 0;反之,若x为正数,!!(!x | (x >> 31) = 1;
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
int isLessOrEqual(int x, int y) {
int sx;
int sy;
int sdiff;
sx = (x >> 31) & 1;
sy = (y >> 31) & 1;
sdiff = ((y + (~x + 1)) >> 31) & 1;
return (((sx ^ sy) & sx) | !sdiff & !(sx ^ sy));
}
考虑到y-x,可能存在溢出情况,分类讨论。如果x, y 异号且x为负,则返回1;如果x, y 异号且x为正,则返回0;如果x,y同号,根据y-x正负判断。
/*
* ilog2 - return floor(log base 2 of x), where x > 0
* Example: ilog2(16) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int ilog2(int x) {
int bitsNumber;
bitsNumber = (!!(x >> 16)) << 4;
bitsNumber = bitsNumber + ((!!(x >> (bitsNumber+8))) << 3);
bitsNumber = bitsNumber + ((!!(x >> (bitsNumber+4))) << 2);
bitsNumber = bitsNumber + ((!!(x >> (bitsNumber+2))) << 1);
bitsNumber = bitsNumber + (!!(x >> (bitsNumber+1)));
return bitsNumber;
}
这题类似用二分搜索最高位为1的位置,比如说找到为第k位,那么结果就是k。注意⚠️为什么用二分?减少运算!
/*
* float_neg - Return bit-level equivalent of expression -f for
* floating point argument f.
* Both the argument and result are passed as unsigned int's, but
* they are to be interpreted as the bit-level representations of
* single-precision floating point values.
* When argument is NaN, return argument.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 10
* Rating: 2
*/
unsigned float_neg(unsigned uf) {
unsigned result;
unsigned tmp;
result = uf ^ (0x80 << 24);
tmp = uf & 0x7FFFFFFF;
if( tmp > 0x7F800000)
return uf;
return result;
}
在最高位置为0情况下,如果大于0x7F800000,一定是NaN,直接返回。如果不大于符号为取反,返回。
/*
* float_i2f - 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.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
unsigned float_i2f(int x) {
unsigned shiftLeft;
unsigned afterShift, tmp;
unsigned absX;
unsigned sign;
unsigned flag;
absX = x;
shiftLeft = 0;
sign = 0;
if(!x) return 0;
if(x < 0) {
sign = 0x80 << 24;
absX = -x;
}
afterShift=absX;
while(1)
{
tmp = afterShift;
afterShift <<= 1;
shiftLeft++;
if(tmp & (0x80 << 24)) break;
}
if((afterShift & 0x01ff) > 0x0100)
flag = 1;
else if((afterShift & 0x03ff) == 0x0300)
flag = 1;
else
flag = 0;
return (sign | (afterShift >> 9) | ((159-shiftLeft) << 23)) + flag;
}
IEEE754表示浮点数,以及Rounding规则的定义题,根据规则来就好了。
/*
* float_twice - Return bit-level equivalent of expression 2*f for
* floating point argument f.
* Both the argument and result are passed as unsigned int's, but
* they are to be interpreted as the bit-level representation of
* single-precision floating point values.
* When argument is NaN, return argument
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
unsigned float_twice(unsigned uf) {
if( (uf & 0x7F800000) == 0 )
uf = ((uf & 0x007FFFFF) << 1) | (0x80000000 & uf);
else if (( uf & 0x7F800000) != 0x7F800000)
uf = uf + 0x800000;
return uf;
}
如果阶码为0,那么就是非规格数,直接将尾数左移1位到阶码域上,其他不变即可。例如 0 00000000 1000…001 变成 0 00000001 000…0010。这样可以做的原因正是由于非规格化数的指数E = 1 - bias,而不是-bias。这样使得可以平滑地从非规格数过度到规格化数。
如果阶码不为0且不是255,那么直接阶码加1即可。
如果阶码为255,那么是NaN,∞,-∞,直接返回。
题目地址: http://csapp.cs.cmu.edu/3e/labs.html
源码:https://github.com/eadren/csapp-datalab/blob/master/bits.c