Datalab实验是关于数据的机器级表示,实验要求实现给定的位级运算符,同时要满足一些要求,如只能使用某些限定的运算符,运算符总数不超过某数字等。第一次刷感觉难度还是很大的。
//1
/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
return ~( ~(~x & y) & ~(x & ~y) );
}
第一题,要求利用 ~ 和 & 实现按位异或。前面几道题还是比较简单的。
思路:比较被熟知的异或表达式是这样的 :
X
o
r
(
x
,
y
)
=
(
!
x
&
y
)
∣
(
x
&
!
y
)
Xor(x, y) = (!x \& y) | (x \& !y)
Xor(x,y)=(!x&y)∣(x&!y)
但是题目要求不能用按位与,所以考虑对这个式子取反:将或逻辑变成与逻辑
!
X
o
r
(
x
,
y
)
=
!
(
!
x
&
y
)
&
!
(
x
&
!
y
)
!Xor(x, y) = !(!x \& y) \& !(x \& !y)
!Xor(x,y)=!(!x&y)&!(x&!y)
将这个式子再取反一次就可以得到Xor了
int bitXor(int x, int y) {
return ~( ~(~x & y) & ~(x & ~y) );
}
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
return 1<<31;
}
求补码int的最小值,很简单。
/*
* isTmax - returns 1 if x is the maximum, two's complement number,
* and 0 otherwise
* Legal ops: ! ~ & ^ | +
* Max ops: 10
* Rating: 1
*/
int isTmax(int x) {
//return 2;
return !(~(x + x + 0x1)) & !(!(x + 0x1));
}
如果 x 是补码int能表示的最大值,返回1,否则返回0.
思路:int 补码能表示的最大值是
0
x
7
f
f
f
f
f
f
f
0x7fffffff
0x7fffffff,可以利用其特征使它向零转换。这个数字左移一位再加一之后是全一的位模式(
0
x
f
f
f
f
f
f
f
f
0xffffffff
0xffffffff),然后按位取反判断是否为0即可。还要注意
x
=
0
x
f
f
f
f
f
f
f
f
x = 0xffffffff
x=0xffffffff,也满足同样的特征,所以要排除这种情况。
/*
* allOddBits - return 1 if all odd-numbered bits in word set to 1
* where bits are numbered from 0 (least significant) to 31 (most significant)
* Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
int allOddBits(int x) {
//return 2;
int mask = 0xaa;
mask = (mask << 8) + mask;
mask = (mask << 16) + mask;
return !((x & mask) ^ mask);
}
要求:判断一个整数是否满足所有二进制权值为奇数的位都是1。
思路:要考察某些特定的位,容易想到可以构造这些位的掩码,来吸取 x 的这些位。将吸取结果与
0
x
a
a
a
a
a
a
a
a
0xaaaaaaaa
0xaaaaaaaa异或,判断结果是否为0即可。
还要注意本实验要求代码中不能出现大于
0
x
f
f
0xff
0xff 的立即数,所以需要用
0
x
a
a
0xaa
0xaa 不断左移来构造掩码。
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
//return 2;
return (~x)+1;
}
给定 x ,返回相反数。计算机中整数取相反数就是求补码,这道题属于常识。
//3
/*
* 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 t1 = !(((~(0x07) & x) >> 3) ^ 0x06);
int t2 = !(((~(0x01) & x) >> 1) ^ 0x1c);
return t1 | t2;
}
要求:判断x是否是ASCII字符 ‘0’~‘9’,即是否
0
x
30
<
=
x
<
=
0
x
39
0x30 <= x <= 0x39
0x30<=x<=0x39。
这道题还是有必要仔细记录一下的,因为后面还有一道题叫“isLessOrEqual” ,要求用位运算实现判断 x <= y,起初我想用一个思路解决这两道题,即将
x
<
=
y
x<=y
x<=y转化成
x
−
y
<
=
0
x-y<=0
x−y<=0再进行补码运算后判断正负号。没想到这个方法用在这两道题上都出了问题。
这道题正确的思路:
将0x30与0x39的二进制形式写出来
0x30 = 0011 0000
0x39 = 0011 1001
可以看出来满足 0 x 30 < = x < = 0 x 39 0x30<=x<=0x39 0x30<=x<=0x39的x可以划分成两种情况:
0011 0xxx //x代表任意二进制数字
0011 100x //总共 2^3 + 2 = 10个数字,正好0x30~0x39也是十个数字
然后就可以分两种情况分别判断了,(代码中的t1和t2)。
/*
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
int conditional(int x, int y, int z) {
//return 2;
int xx = ((!x) << 31) >> 31;
return (~xx & y) | (xx & z);
}
题目要求用位运算实现三目运算符 ‘
?
?
?’,根据三目运算符的逻辑(x ? y : z)来看,可以知道它是类似于做一个二路选择,即当x为真时,选择(信号)y作为输出,否则选择(信号)z作为输出,因此本题可以参考数字逻辑中的二路选择器。将x变成全0或全1的掩码,吸取y和z的所有位。
情况一:当x = 0时,掩码应该是全1的位模式。
情况二:当x != 0时,掩码应该是全0的位模式。
代码中的xx,即用x构造的掩码。
/*
* 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 a = (x >> 31) & 0x1; //x的符号
int b = (y >> 31) & 0x1; //y的符号
int c1 = (a & ~b); // x<0且y>0的情况
int c2 = (~a & b); // x>0且y<0的情况
int e = y + (~x + 0x1); //x-y
int flag = e >> 31;
return c1 | (!c2 & !flag); //如果flag 和 c2 不同则说明了溢出了
}
判断两个数字x,y是否满足 x<=y,满足则输出1.
起初我想将
x
<
=
y
x<=y
x<=y转化成
x
−
y
<
=
0
x-y<=0
x−y<=0再进行补码运算后判断正负号。但是评测时没有得分,查看数据之后才发现我没有意识到x-y存在溢出问题,如果x-y超过int表示范围,判断结果就会出错。所以这道题需要另外的思路。
如果x和y的符号不同,那么正数一定是大于负数的。符号相同则做减法不会溢出,此时可以看差值的符号,详细见代码注释。
/*
* 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 + 0x1)) >> 31) + 0x1;
}
题目:不使用 ’ ! ‘,实现逻辑非,即x = 0输出1,x为非0,输出0.
思路:容易想到如果位模式为全0,那么将其加上0x1后就得到true的逻辑,而全1的位模式,加上0x1后就变成全0,也就是false的逻辑。
那么我们可以定义一组操作,使得当x = 0时,经过操作后结果仍为0,之后加上0x1,就变成了true;当x != 0时,经过这组操作变成全1的位模式,我们要找到这样的操作。
对于补码运算,我们知道除了0(其实还有1000…0,但是这种情况没有例外)的补码是自己本身,其他数字的补码都是其相反数,符号位改变。那么如果用原数x与(~x + 0x1)进行按位或,就可以得到符号位为1,对其进行算数右移即可得到全1的位模式,而0的补码是其本身,经过这套操作,结果还是全0的位模式。
(10000.0的补码也是其本身,但因为其本身符号位就是1,所以 x | (~x + 0x1))后也是全1的位模式,所以这种情况不是例外)。
/* 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 b16,b8,b4,b2,b1,b0;
int sign=x>>31;
x = (sign&~x)|(~sign&x); //如果x为正则不变,否则按位取反(这样好找最高位为1的,原来是最高位为0的,这样也将符号位去掉了)
// 不断缩小范围
b16 = !!(x>>16)<<4; //高十六位是否有1
x = x>>b16; //如果有(至少需要16位),则将原数右移16位
b8 = !!(x>>8)<<3; //剩余位高8位是否有1
x = x>>b8; //如果有(至少需要16+8=24位),则右移8位
b4 = !!(x>>4)<<2; //同理
x = x>>b4;
b2 = !!(x>>2)<<1;
x = x>>b2;
b1 = !!(x>>1);
x = x>>b1;
b0 = x;
return b16+b8+b4+b2+b1+b0+1; //+1表示加上符号位
}
题意:对于整数x,输出用补码表示x,最少需要多少二进制位。
这道题应该是DataLab里最难的一道题了,我一开始是几乎没有思路的,容易知道其实这道题就是求x的二进制表示中,最高位的1在第几位,同时对于负数考虑符号位,但之后就不会做了。学习了别人的思路之后才明白了一点。类似二分查找。具体见代码。
第二部分是浮点运算,这部分放宽了对程序的限制,现在可以使用逻辑运算符’&&‘,’||',以及if语句和while语句了。
/*
/*
* 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;
int sign = uf & (1<<31);
if(exp==0)
return uf<<1 | sign;
if(exp==255)
return uf;
exp = exp + 0x1;
if(exp==255)
return 0x7f800000 | sign;
return (exp << 23) | (uf & 0x807fffff);
}
要求:输出浮点数2.0*x。其中返回值都以unsigned的形式给出,但它们都会被解释称float的位模式。
思路:如果不考虑0,溢出和NAN的情况,实际上只需要把原浮点数的阶码+1即可。
/*
* floatFloat2Int - Return bit-level equivalent of expression (int) f
* for floating point argument f.
* Argument is passed as unsigned int, but
* it is to be interpreted as the bit-level representation of a
* single-precision floating point value.
* Anything out of range (including NaN and infinity) should return
* 0x80000000u.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
int floatFloat2Int(unsigned uf) {
int exp = ((uf & 0x7f800000)>>23) + (~0x7f + 0x1);
int M = uf & ((1 << 23) + (~0x1 + 1));
int sign = uf & (1<<31);
int flag = 1<<22, te = exp;
int res = 0;
if((exp) > 30)
{
return 0x80000000u;
}
else if(exp < 0)
{
return 0x0;
}
while(flag && te)
{
if(M & flag)
{
res = res + (1 << (te-1));
}
flag = flag >> 1;
te = te + (~0x1 + 1);
}
res = res + (1<
题意:对于浮点数 f 的位模式(用unsigned类型给出),返回(int)f的值。
这道题倒不是很难,就是有点复杂。首先需要依据阶码,判断0的情况以及INF的情况;随后就是将unsigned类型给出的位模式,化成1.xxxxxx…x
×
\times
×
2
e
x
p
2^{exp}
2exp的形式,写一个类似二进制数转十进制的程序。我自己最初就像平时写进制转换程序那样写了一个while循环,但是在看了别人的思路后发现其实不用循环,因为数字已经是用位模式表示出来的了,用移位就可以实现转换。
/*
* 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 < -126)
{
return 0;
}
else if(x > 127)
{
return (0x7f << 24) + (0x1 << 23);
}
return (x + 0x7f) << 23;
}
没想到最后一道题反而比较简单,这道题要求输出
(
2.0
)
x
(2.0)^x
(2.0)x。当无法表示时按情况输出
0
0
0或
+
I
N
F
+INF
+INF。根据
I
E
E
E
754
IEEE754
IEEE754标准很容易得到答案。所有
2
x
2^x
2x的数字都可以表示为
1.00......0
×
2
e
x
p
1.00......0\times2^{exp}
1.00......0×2exp
即位模式
0
x
x
x
x
x
x
x
x
00000000000000000000000
0\ xxxxxxxx\ 00000000000000000000000
0 xxxxxxxx 00000000000000000000000
将x加上偏移量后,移位到阶码处即可。
断断续续做了几天,终于把DataLab做完了。贴一个DataLab满分截图。