实验说明
首先下载好实验源码,github甚至有部署作业的shell脚本可以使用。datalab目录结构如下。
同时确保你仔细阅读两个文档
./README-datalab #介绍作业内容
./datalab-handout/README # 测试工具和辅助工具文档
btest
btest是测试bits.c作业的程序,检查你的代码是否能够通过测试用例。
# 编译btest文件, 生成测试程序
make btest
使用方式如下
./btest -h Usage: ./btest [-hg] [-r ] [-f [-1|-2|-3 ]*] [-T ] -1 Specify first function argument -2 Specify second function argument -3 Specify third function argument -f Test only the named function -g Format output for autograding with no error messages -h Print this message -r Give uniform weight of n for all problems -T Set timeout limit to lim
helper
实验还提供了2个文件,fshow和ishow,可以输入整数,让程序帮你将整型和浮点数表示出来。
unix> ./ishow 0x27
Hex = 0x00000027, Signed = 39, Unsigned = 39
unix> ./ishow 27
Hex = 0x0000001b, Signed = 27, Unsigned = 27
unix> ./fshow 0x15213243
Floating point value 3.255334057e-26
Bit Representation 0x15213243, sign = 0, exponent = 0x2a, fraction = 0x213243
Normalized. +1.2593463659 X 2^(-85)
linux> ./fshow 15213243
Floating point value 2.131829405e-38
Bit Representation 0x00e822bb, sign = 0, exponent = 0x01, fraction = 0x6822bb
Normalized. +1.8135598898 X 2^(-126)
本次实验需要自己实现的文件
bits.c
动手写实验
- 本次实验只能使用下面的运算符
! ˜ & ˆ | + << >>
- 使用的常量不超过 8 bits
部分题目不太好写,还是想了很久都没解决,参考了其他博客实现思路。
bitXor
异或可以拆为2个部分,离散数学或者数字电路中都有介绍(德摩根律)。
int bitXor(int x, int y)
{
// 先不要强制用 ~和&直接写,可以用|等其他的赋值,再拆解
/*
1. ~(x & y) & (x | y)
2. ~(x & y) & ~(~x & ~y) 这一步类似于集合操作对 Union 取反(德摩根律)
*/
return ~(x & y) & ~(~x & ~y);
}
tmin
直接左移31位得到
int tmin(void)
{
// 1逻辑左移31位, 100...0
return 1 << 31;
}
isTmax
Tmax判断,使用Tmin和Tmax的关系来实现。
int isTmax(int x)
{
/*
不能使用<<操作, 那就利用Tmin和Tmax的关系
-Tmin = ~Tmin + 1 = Tmin
Tmax = ~Tmin
*/
// int xplus = x + 1; // if tmax, then x+1 equals tmin
// int y = ~(x ^ xplus); // if x is tmax, then tmin ^ tmax equals tmin
// return !(y+!xplus); // special case: 0
return !(~(x^(x+1))+!(x+1)); // 7 operators
}
allOddBits
这个函数就是实现判断输入x是否它的奇数位全置为1。如果x奇数位全为1一定满足如下表达式。
注释中也给出了提示,最典型的allOddBits就是0xAAA...A,可以采用0xAAA...A位作为掩码进行与运算和异或运算判断输入的x是否为全奇数位。
int allOddBits(int x)
{
int mask = 0xAA + (0xAA<<8);
mask = mask + (mask << 16);
return !((mask & x) ^ mask);
}
negate
最简单的题目,课堂上还讲过,two's complement取反+1(另外,Tmin这样做还是Tmin自己)
int negate(int x)
{
return ~x+1;
}
isAsciiDigit
通过最高位和位移运算判断输入是否在范围内。
- 如果超过0x39,相加后最高位进位,通过右移判断最高位是否大于上界
- 如果小于0x30,取相反数然后相加,如果最高位还是1,说明小于0x30,再右判断是否小于下界
int isAsciiDigit(int x)
{
int mostSignificant = 1<<31; // 记录负号
int upperBound = ~(mostSignificant|0x39);
int lowerBound = ~0x30 + 1;
// 如果不在范围内, 最高位就是1
upperBound = mostSignificant&(upperBound+x)>>31;
lowerBound = mostSignificant&(lowerBound+x)>>31;
return !(upperBound | lowerBound);
}
conditional
使用位运算来实现三目运算符。
思路:x为判断条件,那么肯定要先转换成01,采用逻辑非将其变为0和1。通过掩码形式返回结果,1对应全1,0则为全0,可以用补码来实现。
int conditional(int x, int y, int z)
{
x = !!x; // convert to 0 or 1
x = ~x+1; // convert to mask
return (x&y)|(~x&z);
}
isLessOrEqual
通过符号位和数字做差判断大小。
- 符号判断:如果x为正,y为负,直接放回0,0&其他值都是0;如果x为负,y为正返回1。
- 如果x, y 符号相同,判断x-y<=0成立,
/*
* 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 signx = (x>>31)&1;
int signy = (y>>31)&1;
// 比大小, 看最高位
int sub = x+(~y+1);
int le = !((sub&(1<<31)) ^ (1<<31)) | // 取出符号位, 如果为负数, 这一部分为1
!(sub^0); // 不为0 这一部分为0
return (!(~signx & signy)) & // 符号判断
((signx & ~signy)|le); // 相同符号大小判断
}
logicalNegate
考虑2个特殊的值即可,0和Tmin,除了这2个数的相反数是本身,其他都是不同的补码。但是0的符号位和补码进行或运算后是0,而Tmin不同。Tmin符号位和Tmin补码进行或运算后是0xffff。其他值操作后也都是0xffff。
/*
* logicalNeg - implement the ! operator, using all of
* the legal operators except !
* Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int logicalNeg(int x)
{
return ((x|(~x+1))>>31)+1;
}
howManyBits
计算描述输入值x需要的最小位数,对于同一个值,可能有多个不同位数表示,-5=1011=11011=111011。负数的补码前面有多个1,但是最终结果都一样。对于正数找最高位1,负数找最高位0。
取出高位后就对剩下的数据位进行二分法判断是否需要高位。
/* howManyBits - return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(298) = 10
* howManyBits(-5) = 4
* howManyBits(0) = 1
* howManyBits(-1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int howManyBits(int x)
{
int sign = x>>31;
x = (sign&~x) | (~sign&x); // 找到x的最高位1
// 二分法拆分bits
/*
比如 0100 1100 1100 1100 1100 1100
1. 高16位中有 0100 1100, 那只要关注高16位, x移动16位. +16
2. 再取高16位的高8位, 高8位为0, 没有内容, x移动0位. +0
3. 看当前0100 1100的高4位,x移动后有0100,那就看0100,x移动4位. +4
4. 再看0100的高2位, x移动后变为01, x移动2位. +2
5. 01再移动1位, 为0. x移动1位. +1
6. 最后符号位+1
*/
int b16 = (!!(x>>16)) << 4;
x = x>>b16;
int b8 = (!!(x>>8)) << 3;
x = x>>b8;
int b4 = (!!(x>>4)) << 2;
x = x>>b4;
int b2 = (!!(x>>2)) << 1;
x = x>>b2;
// 最后一步这里可能不太好理解, 这里是把最后一个有效位1给移出去了,然后变为了0
int b1 = (!!(x>>1));
int b0 = x >> b1;
return b16+b8+b4+b2+b1+b0+1;
}
floatScale2
输入一个unsigned int数据,它表示的是一个单精度浮点数,先将其double后按照unsigned返回。但是传进来的uf需要进行下面的判断
- uf为非规格化数据,exp=0,此时M<1,进行左移
- uf为规格化数据,exp≠0,将exp+1即可,还要考虑溢出问题,浮点数溢出就是无穷(infinity)
参考CSAPP教材 Floating Poing 2.4章节来看,这道题肯定没问题。
/*
* floatScale2 - 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 floatScale2(unsigned uf)
{
int exp = (uf&0x7f800000)>>23;
if(exp==0xff) return uf; // NaN or Infinity
unsigned sign = uf & (1<<31);
// denormalized
if(exp==0) return uf<<1|sign;
// overflow
if(++exp==0xff) return sign|0x7f800000;
// normalized
return exp<<23 | (uf&0x807fffff);
}
floatFloat2Int
将单精度浮点数转换为整型,分类讨论
-
E<0,浮点数为非规格化或者是1.xxx乘2的-n次幂,这种情况小于0,返回0
-
E>31,超过int能够表达的最大值,按照注释返回0x80000000
-
0<E<31
- 23<E<31,frac部分全都在小数点左边,左移
- 0<=E<=23,frac需要右移
int floatFloat2Int(unsigned uf)
{
unsigned exp = (uf & 0x7f800000)>>23;
int E = exp - 127;
int frac = (uf & 0x7fffff) | 0x800000;
if(E<0) return 0; // 小于1的返回0
if(E>31) return 0x80000000; // 超过int最大
// 移动小数点
if(E>23) {
frac <<= (E-23);
} else {
frac >>= (23-E);
}
if(!((uf>>31) ^ (frac>>31))) { // 左移符号位不变
return frac;
}
// 符号位变为负, 溢出
if(frac>>31) {
return 0x80000000;
}
// 取补码
return ~frac + 1;
}
floatPower2
输入一个x,返回
返回值是单精度的unsigned,如果值太小返回0,如果太大返回Infinity。首先frac字段是全为0的,因为是2的n次方。这里的x实际上就是指数E,按照下面的式子计算即可。
/*
* floatPower2 - Return bit-level equivalent of the expression 2.0^x
* (2.0 raised to the power x) for any 32-bit integer x.
*
* The unsigned value that is returned should have the identical bit
* representation as the single-precision floating-point number 2.0^x.
* If the result is too small to be represented as a denorm, return
* 0. If too large, return +INF.
*
* Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
* Max ops: 30
* Rating: 4
*/
unsigned floatPower2(int x)
{
if(x>127) return 0x7f800000; // return Infinity
if(x<-126) return 0;
return ((x+127)<<23); // x = E = exp -bias, exp = x + bias
}