将难度分为5个档次,仅供参考。
要求全程可以直接使用的数范围在0x00-0xFF之间
要求:仅用~和&实现异或运算;
难度:1星
思路:直接采用数字逻辑中学到的就可以推导出计算公式:
代码表达如下:
/* 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) {
int a1, a2, re;
a1 = ~(x & (~y));
a2 = ~((~x) & y);
re = ~(a1 & a2);
return re;
//不使用中间变量:
int bitXor(int x, int y) {
return ~( (~(x & (~y))) & (~((~x) & y)));
}
要求:x为最大数,返回1;否则返回0。
难度:3星
思路:(这一题没有想到特别好的思路,如果有好的想法欢迎分享)
最大数为0x7FFFFFFF,最开始发现最大数加1后的值与最大数异或可以得到全1,所以用此作为判断依据。
后来发现还有一个特例也会有相同结论:0xFFFFFFFF,所以还需要排除全1的这种情况。
可以发现(其实后来都是凑出来的orz),全1加上1后是全0,与最大数加上1得到1不同,所以以此作为切入点进行判断。
语言难以表达,上代码:
/* 2
* judgeTMax - returns 1 if x is the maximum of two's *complement number, and 0 otherwise
* Legal ops: ! ~ & ^ | +
* Max ops: 10
* Rating: 1
*/
//便于理解版:
int judgeTMax(int x) {
int a1, a, b, c;
a1 = !(x + 1) + 1;
//如果x为最大数,a1就是1,下述判断方法与最初想的一样;
//如果x为全1,a1是2,则不会干扰判断;
//主要改善的点在与把x+1改为了x+a1
//而a1是通过找到全1和最大数之间不同点入手构造的一个掩码。
a = x + a1;
b = ~(a ^ x);
c = !b;
return c;
}
//去掉中间变量,最终代码:
int judgeTMax(int x) {
return !(~((x + (!(x + 1) + 1)) ^ x));
}
要求:判断给定二进制补码的偶数位是不是全为1(从右向左数,从0开始计数的偶数位)
难度:3星
思路:
1.先构造出偶数位全为1,奇数位全为0的数0x55555555,构造方法为通过0x55,不断进行左移再加上原来的数。
2.进行判断时先将0x55555555和x进行与操作,使得x的奇数位全为0,偶数位保持不变,排除奇数位对判断的影响。
3.再将2中得到的数和0x55555555进行异或操作,如果x中存在偶数位不为1,则得到的结果中对应的位为1——不是全0;如果x满足条件,偶数位全1,则得到结果全0。进行!得到答案。
代码如下:
/* 3
* allEvenBits_one - return 1 if all even-numbered bits in an integer are set to 1
* where bits are numbered from 0 (least significant) to 31 (most significant)
* Examples allEvenBits_one(0xFFFFFFFD) = 1, allEvenBits_one(0xA5555555) = 0
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
//便于理解版:
int allEvenBits_one(int x) {
int a = 0x55,b,c,d,e,y;
b = (a << 8);
c = a + b;
d = (c << 16);
e = c + d;
y = !((e & x)^e);
return y;
}
//最终代码:
int allEvenBits_one(int x) {
int a = ((0x55 << 8) + 0x55);
mask = (a << 16) + a;
return !((mask & x) ^ mask);
}
难度:1星
思路:求对应的相反数就是取反加1
代码入下:
/* 4
* negative_transform - return -x
* Example: negative_transform(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negative_transform(int x) {
return (~x+1);
}
难度:2星
思路:
观察0x30-0x39可以发现,0x30-0x37最后六位都是110xxx形式,后面三位数取任何值都落在该区间上;0x38和0x39则为11100x形式;
由于x取任何值都可以,故只需要对110和11100进行判断。
判断方法是移位然后异或。
代码如下:
/* 5
* judgeAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
* Example: judgeAsciiDigit(0x35) = 1.
* judgeAsciiDigit(0x3a) = 0.
* judgeAsciiDigit(0x05) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 3
*/
//便于理解版:
int judgeAsciiDigit(int x) {
int zerotoseven = !((x>>3) ^ 0x06);
int eightandnine = !((x>>1) ^ 0x1c);
int re = zerotoseven | eightandnine;
return re;
}
//最终代码:
int judgeAsciiDigit(int x) {
return (!((x>>3) ^ 0x06)) | (!((x>>1) ^ 0x1c));
}
目的:通过位运算实现条件运算符
难度:3星
思路:
需要构造一个掩码全0或全1,通过和y,z做与运算进行选择。
所以构造这样一个mask:当x == 0 时,mask为全1,将z选择出来;当x非0时,mask全0,用~mask将y选择出来。
/* 6
* contitionExpr - same as x ? y : z
* Example: contitionExpr(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
//便于理解版:
int contitionExpr(int x, int y, int z) {
int mask;
mask = ~(!x) + 1;//当x == 0 时,mask为全1;当x非0时,mask全0;
int re = (y & ~mask) + (z & mask);//当x == 0 时,mask为全1,将z选择出来;当x非0时,mask全0,~mask将y选择出来。
return re;
}
//最终代码:
int contitionExpr(int x, int y, int z) {
return (y & ~(~(!x) + 1)) + (z & (~(!x) + 1));
}
目的:通过位运算实现逻辑非运算
难度:2星
思路:逻辑非运算主要用与判别0和非0,所以考虑0 和非0 的不同点。
x为0时,x = -x = 0,符号位均为0
x不为0时,除了0x80000000外其余的数均满足 x不等于-x
考虑0x800000000的特殊情况,此时满足 -x = x 但符号位均为1
所以当 x = -x 且符号位均为0时返回1,其余时候返回0;关键是判断符号位,最后的判断有利用前面第六题条件运算符的结果
/* 7
* logicalNot - implement the ! operator, using all of
* the legal operators except !
* Examples: logicalNot(3) = 0, logicalNot(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 3
*/
//便于理解版:
int logicalNot(int x) {
int a1,a2;
a1 = (x >> 31);
a2 = ((~x + 1) >> 31);
//只有当x == 0时,a1和a2都是0,其余情况至少存在一个全1
int a3 = a1|a2;//只有当x == 0时,a3是全0,其余情况全1
int re = (0x01 & ~a3) + (0x00 & a3);//利用条件运算符的表达式
return re;
}
//最终代码:
int logicalNot(int x) {
int mask = (x >> 31)|((~x + 1) >> 31);
return (0x01 & ~mask) + (0x00 & mask);
}
难度:5星
思路:有两个难点,一个是需要找到负数和正数的统一处理方法,另一个是需要找到求出具体位数的简便方法(不能用循环)。
对于第一个难点:所有的负数取反之后都是正数(不加1),这样得到的正数的最少位数与原本负数的位数相同(可以通过验证得到,但是严谨的证明不是很清楚),所以可以将此问题转化为求正数的最小二进制位数。
对于第二个难点:通过类似二分法的方法解决。
下面只对其中第一步进行详细解释,剩下的可以类推:
1.用bit16 = !!(x >> 16) << 4来判断高16位是否有1。
2.因为!的优先级高于<<,所以先进行!!(x >> 16)。
3.由于都是对正数进行求解,所以在右移16位后,前16位全为0,后16位是原本的高16位。
4.如果高16位存在至少一位为1,则!(x>>16)为0则再进行一次!操作得到!!(x >> 16) = 1(0x00000001),否则!!(x >> 16) = 0x00000000
5.进行<<4的操作使得值变为0x00010000或者0x00000000,即16和0,目的是为了计数和下一步缩小判定范围。
6.如果高十六位为存在至少一位为1,则这个数至少有16位,接下来需要做的就是再进行进一步的定位来确定大于16位的部分还需要多少位,所以用x = x >> bit16;将x的低16位移走,将高16位移动到低16位的位置,对低16位(原本的高16位)进行判断
如果高16位中没有1,则继续对低16位进行判断,不用进行移位操作(bit16为全0)
以上就是第一步代码 bit16 = !!(x >> 16) << 4; x = x >> bit16; 的意思。
代码如下:
/* 8
* leastBeats - return the minimum number of bits required to represent x in
* two's complement, remember the a bit of sign is needed.
* Examples: leastBeats(12) = 5 (most significant)
* leastBeats(298) = 10
* leastBeats(-5) = 4
* leastBeats(0) = 1
* leastBeats(-1) = 1
* leastBeats(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
//便于理解版:
int leastBeats(int x) {
int sign,bit16,bit8,bit4,bit2,bit1,bit0,re;
sign = x >> 31;//得到符号,如果符号位为0,sign全0,否则sign全1
x = (x & ~sign) | ((~x) & sign);//将x统一变为对应(取反不加1)的正数
//下面开始对x的位数进行判断计数
bit16 = !!(x >> 16) << 4;//判断32位数中高16位是否有1。
x = x >> bit16;
bit8 = !!(x >> 8) << 3;//在低16位中判断高八位是否有1
x = x >> bit8;
bit4 = !!(x >> 4) << 2;//在低8位中判断高4位是否有1
x = x >> bit4;
bit2 = !!(x >> 2) << 1;//在低4位中判断高2位是否有1
x = x >> bit2;
bit1 = !!(x >> 1);//在低2位中判断高1位是否有1
x = x >> bit1;
bit0 = x; //判断最低位是否为1
re = bit16 + bit8 + bit4 + bit2 + bit1 + bit0 + 1; // 正数需要符号位一位
return re;
}
//最终代码也没啥变化(去掉了注释,bit0和re)
int leastBeats(int x) {
int sign,bit16,bit8,bit4,bit2,bit1;
sign = x >> 31;
x = (x & ~sign) | ((~x) & sign);//将x统一变为正数来看
bit16 = !!(x >> 16) << 4;//判断高16位是否有1
x = x >> bit16;
bit8 = !!(x >> 8) << 3;
x = x >> bit8;
bit4 = !!(x >> 4) << 2;
x = x >> bit4;
bit2 = !!(x >> 2) << 1;
x = x >> bit2;
bit1 = !!(x >> 1);
x = x >> bit1;
return bit16 + bit8 + bit4 + bit2 + bit1 + x + 1;
}
难度:5星
思路:和有一个数字逻辑的表达也很相似,主要通过异或实现。
比如X = X1,X2,X3,X4是一个四位二进制数,则将X与X>>1异或之后得到的结果设为b1,b2,b3,b4,则的b4就存放的是X3和X4的奇偶性(奇数个1则b4 = 1),b2就存放的是X1和X2的奇偶性。奇数位无意义。
利用这个特性来求解,具体看代码:
/* 9
* bitsOneOdd - return whether the number of 1 over all bits of an integer is odd or even
if number is odd, return 1, else return 0
* Examples: bitsOneOdd(1) = 1 0x01
* bitsOneOdd(2) = 1 0x10
* bitsOneOdd(3) = 0 0x11
* Legal Ops: ! ~ & ^ << >>
* MaxOps: 20
* Rating: 4
*/
//便于理解版:
int bitsOneOdd(int x){
int a1,a2,a3,a4,a5,re;
a1 = x ^ ( x >> 1 );
//a1的偶数位存放着x中每两位的奇偶性
a2 = a1 ^ ( a1 >> 2);
//偶数位与偶数位相异或,a2中每四位中最低位得到a1中相邻偶数位的奇偶性
a3 = a2 ^ ( a2 >> 4);
//同理类推,a2中每四位中最低位与相邻的高四位中最低位相异或,a3中每8位中最低位得到a2中相邻每四位中最低位的奇偶性
a4 = a3 ^ ( a3 >> 8);
a5 = a4 ^ ( a4 >> 16);//最终最低位得到了x的奇偶性
re = 0x01 & a5;//将其余位置0,取出最后一位作为结果,
return re;
}
//最终代码去掉了re:
int bitsOneOdd(int x){
int a1,a2,a3,a4,a5;
a1 = x ^ ( x >> 1 );
a2 = a1 ^ ( a1 >> 2);
a3 = a2 ^ ( a2 >> 4);
a4 = a3 ^ ( a3 >> 8);
a5 = a4 ^ ( a4 >> 16);
return 0x01 & a5;
}
难度:2星
思路:负溢出就是两个负数相加变为0或者正数,正溢出就是两个正数相加变为负数。
具体代码如下:
/* 10
* plusFlow - return whether the sum of two integer x and y overflows(two's complement number)
* if overflow or underflow, return 1
* Examples: plusFlow(0xffffffff, 0x8000000)=1, plusFlow(1, 1)=0
* Legal Ops: + - ^ && || ! < > ==
* Maxops: 15
* Rating: 1
*/
int plusFlow(int x, int y){
int sum = x + y;
int neg_over = (x < 0 && y < 0) && (sum > 0 || sum == 0);
int pos_over = (x > 0 && y > 0 && sum < 0);
return neg_over || pos_over ;
}
难度:4星(其实感觉看起来不难,但当时做的时候好像不太行orz
具体代码如下:
/* 11
* minusFlow - return whether the subtraction of two integer x and y overflows(two's complement number)
* if overflow or underflow, return 1
* Examples: minusFlow(0xffffffff, 0x80000000)=1, minusFlow(1, 2)=0
* Legal Ops: + - ^ && || ! < > ==
* Maxops: 15
* Rating: 2
*/
int minusFlow(int x, int y){
int sum = x - y;
int over1 = (!(x < 0)) && y < 0 && ( sum < 0);
int over2 = x < 0 && y > 0 && sum > 0;
return over1 || over2;
}
难度:5星(有最多10个符号限制)
思路:
已知满足x && mu/x == y (mu 是 x*y)就是不溢出,但是有一个特例就是0xffffffff, 0x80000000相乘时,是溢出的,但是判断会出错好像是会core dumped,直接报错(也是整数除法溢出的唯一一个情况)
所以需要用除法之外的其他判定条件来排除这种情况,也就是需要说清楚在这种情况下是溢出的;
这也导致不能从不溢出出发,而需要从什么情况会溢出出发来写代码。
/* 12
* multiplyFlow - return whether the multiplication of two integer x and y overflows(two's complement number)
* if overflow or underflow, return 1
* Examples: multiplyFlow(0xffffffff, 0x80000000)=1, multiplyFlow(1, 2)=0
* Legal Ops: * / ^ && || ! < > ==
* Maxops: 10
* Rating: 3
*/
//最开始版本,会出现报错floating point exception (core dumped)
int multiplyFlow(int x, int y){
int mu = x*y;
int re = (x && y && (mu/x != y || mu/y != x));
return re;
}
//改进版
int multiplyFlow(int x, int y){
int mu = x*y;
int re = (x < 0 && y < 0 && mu < 0) || (x && !(mu/x == y));// ||的先后顺序不能变,否则也会core dumped
//因为是利用前面先排除了会dumped的情况,再进行除法
return re;
}
//最终版:(后来减少一个符号)
int multiplyFlow(int x, int y){
int mu = x*y;
return ((x ^ y) > 0 && mu < 0) || (x && !(mu/x == y));
}
难度:4星
思路:其实就是浮点数的运算,对各种情况分类处理然后进行合并就好。需要考虑到规格化数,非规格化数,0,NaN,Inf。
因为最开始对浮点数运算不够理解,参考了别的文章才写出来第一个,后来进行合并化简又参考了知乎专栏的文章
代码很清楚了,注释也挺详细,直接上代码:
/* 13
* twoTimesFloat - 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: 3
*/
//最初版本:
unsigned twoTimesFloat(unsigned uf) {
int S = uf & (1 << 31);//符号位
int E = (uf >> 23) & 0xff;//阶码
int mask = (1 << 23) - 1;//23个1
int M = mask & uf;//尾数
if((!E & (M == 0)) || (E == 0xff)) //0,NaN,无穷大的情况下返回自身
return uf;
if(!E) //非规格化数
{
M = (M << 1) & mask;
if(1 & (M >> 22))//尾数最高位为1
E += 1;
}
else//规格化数
{
//判断阶码是否溢出
if(E - 0xff == 0) //加1后阶码溢出,返回无穷大
{
M = 0;
E = 0;
}
else
E += 1;
}
E <<= 23;
return S|E|M;
}
//改进/最终版:
unsigned twoTimesFloat(unsigned uf) {
int E = (uf >> 23) & 0xFF;
if (E == 0xFF)//NaN或Inf
return uf;
if (E == 0)//非规格化数或0
return (uf << 1) | (uf & (1 << 31));
return uf + (1 << 23); //正常计算,阶码+1,如果阶码溢出,返回Nan
}
难度:4星
思路:主要分三种情况,超出范围的返回题目指定特殊值,不能表示的小数返回0,其余正常进行运算返回
具体看代码和注释:
/* 14
* float2Int - 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 float2Int(unsigned uf) {
int mask, EV, frac, i;
mask = 1 << 31;//指定的特殊值,也有掩码作用,最后一步用于取符号
EV = ((uf >> 23) & 0xFF) - 127;//计算出EV 阶(指数)
//两种特殊情况
if (EV > 31) //超出int表示范围,因为还有隐藏的1,所以是31不是32
return mask;//返回指定的特殊值
if (EV < 0)//小数无法用int表示,舍为0
return 0;
frac = (uf & 0x007fffff) | 0x00800000; //取出尾数,并且包括隐含的1
i = (EV > 23) ? (frac << (EV - 23)) : (frac >> (23 - EV)); //求出int可以表示的整数值,即将阶的部分通过移位计算出来
return (uf & mask) ? -i : i; //根据符号决定正负
}
难度:3星
思路:按照阶的大小分类讨论,float中规格化数阶码最小为1(阶最小为-126),阶码最大为254,(阶最大为127);
所以阶小于等于0时返回0;阶大于等于255时,返回INF
其余情况为正常阶码,可以正常表示
具体代码如下:
/* 15
* twoPowerInt - 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 twoPowerInt(int x) {
int E = x + 127;
if (E <= 0)//为0 或者难以表示的非规格化数,返回0
return 0;
if (E >= 0xFF)//过大无法表示或者为INF和NaN,都返回+INF
return (((1 << 10) - 1 ) << 23 ) - (1 << 31);
return E << 23;//正常阶码,正常表示
}
指路本次lab和笔记参考过觉得很不错的文章:
参考文章1
参考文章2
如果本文不错记得点个赞哦!谢谢阅读!