做题的时候看了好多博客,一点一点更深入的了解、学习。不得不说lab真的很好,学到很多。
做题准备我已经在上一个博客提到了,建议写之前看一下README。
运用~和& 实现x^y
这里可以用一个真值表
x | y | x^y |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
我们可以得到
但由于题目要求不可以运用“或运算”,所以这时候要用到德摩根定律
非(P 且 Q) = (非 P) 或 (非 Q)
非(P 或 Q) = (非 P) 且 (非 Q)
可以得到
即可得出本题答案
/*
* 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));
}
求整型数补码所表示的最小值
注意实现这道题所用到的常数不能超过8bit
32位机,整型数最小值为0x8000 0000
由于题目要求,所以我们不能直接返回0x80000000
我们可以看0x8000 0000 这个数字的本质其实就是1后面31个0(二进制)
那么根据题目要求,我们可以使用左移运算符实现
那么答案出来了,0x1<<31即可实现
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {//求整型数补码所表示的最小值
return 0x1<<31;
}
同理,得出答案之后可以思考一下是不是将"0x2<<30"也可以实现呢?
大家可以试着检验一下(如何检验上一篇文章提到过)
判断x是不是补码表示的最大值
我们知道补码表示的最大值为0x7FFF FFFF(二进制中最高位为0,其他位为1)
好知道了最大值,我们来找这个值的特点
当最大值加1时为 0x8000 0000
恰好与最大值按位取反相同
所以我们可以根据这两者按位异或的运算结果来判断x是否是补码的最大值
不过有一种特殊情况:
当x为0xFFFF FFFF时因为加一会发生溢出得到的结果与其按位取反相同(都为0)
所以我们要去掉这种情况
得到的答案即为
/*
* 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) {
int tmin = x+1;
int equal = tmin^(~x);
return (!!tmin)&(!equal);
}
如果x中的所有奇数位都为1,则返回1,不是则返回0
所有奇数为1,偶数为0的情况得到的值为0xAAAA AAAA
做过前面的题,这道题在思考的时候很快就会想到
将x直接与0xAAAA AAAA按位“与运算”
得到的值如果与0xAAAA AAAA相等则返回1,不相等返回0(异或运算)
但由于这道题所用到的常数不能超过8bit
跟第二题类似,我们要得到0xAAAA AAAA则要用到左移运算
/*
* 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) {
int a_8 = 0xAA;
int a_16 = (a_8 << 8)|(a_8);
int a_32 = (a_16 << 16)|(a_16);
int equal = (x & a_32) ^ a_32;
return !equal;
}
返回 -x(不能用到“-”)
我们知道x的负数等于x按位取反加一
即可得到
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
return (~x) + 1;
}
判断 0x30 <= x <= 0x39,是返回1,不是则0
由于这道题判断x对于0x30 和 0x39 大小,可以相减看得到的值大于零还是小于零
但由于这道题不可以使用“-”
那么根据上一道题我们知道 -x = (~x) + 1
得到结果之后我们如何知道结果的正负呢?
我们知道正数的符号位为0,负号为1,那么只需知道符号位即可得到结果的正负
符号位直接左移31位就可以得到
/*
* 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) {
//x-0x30>=0
int a = x+(~0x30+1);
int b = 0x39+(~x+1);
int c = 0x1<<31;
//return !(a>>31)&!(b>>31)
return !(a&c) & !(b&c);
}
实现 x ? y : z (x不为0返回y,为0返回z)
根据判断x的值返回y或z
对于x是否为0,我们可以采用两次!运算
if x != 0 !!x == 1 return y
if x == 0 !!x == 0 return z
为了区分1和0,并获得全1和全0的二进制表示,这里将得到的值进行取反加一的操作得到condition,这样就可以从 1,0变成-1,0(只有通过全1和全0的二进制位与y、z进行位运算才能实现题目要求)
将condition与y按位与,~condition与z按位与
最后可得
/*
* 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) {
int a = !!x;
int condition = (~a)+1;
return (condition&y)|(~condition&z);
}
如果 x<=y ,则返回1,否则返回0
根据上面做题经验我猜你现在想到的是
y+(~x+1)>=0
想法是对的,但在有符号数的加减法中我们应该考虑的一个很重要的问题--溢出
当 y<0 x>0时,得到的结果y-x>=0 但这是不正确的,所以我们要去掉这个方法
当y>0 x<0 时,得到的结果y-x<0 这也是不正确的,此时无论如何y-x都是大于零的,所以在结果中要加上这个方法
那么此时用f_x和f_y来存x、y的正负,即
f_x = x>>31;
f_y = y>>31;
再用of_1 、of_2判断x、y正负
of_1 = !f_x & f_y; y<0 x>0
of_2 = f_x & !f_y; y>0 x<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 t = y+(~x+1);
int a = t>>31;
int f_x = x>>31;
int f_y = y>>31;
int of_1 = !f_x & f_y;
int of_2 = f_x & !f_y;
return of_2|((!of_1)&(!a));
}
实现“!”运算符
当 x == 0 时 ,得到的值为1
其他值,得到的值为0
这里要用到0的性质,0的相反数还是0,按位“或运算”得到的值还是0(最高位也为0)
其他值与相反数按位“或运算”得到的最高位为1(值与相反数总有一个是负数)
那么即可得到答案
/*
* 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) {
int t = (~x+1);
int c = (t|x)>>31;
return c+1;
}
⚠️注意这里的右移是逻辑右移
使用补码时最少需要多少比特位
例子:
howManyBits(12) = 5
howManyBits(298) = 10
howManyBits(-5) = 4
howManyBits(0) = 1
howManyBits(-1) = 1
howManyBits(0x80000000) = 32
这里我们以12为例
0000 0000 0000 0000 0000 0000 0000 1100
实际上要得到12只需要4+1(符号位)=5位即可
这里我们可以看出,计算一个数最小需要多少比特位的问题转化成了从最高位到最低位寻找第一个数据为1的问题
这里我们使用类似于二分的思想
先看高低16位(判断是否存在数据为1的比特位),在看8位,4,2,1......
这里用 flag = !!(x>>16);
如果flag结果为1,表示高16位中存在一位或者多位数据为1的比特位
为0表示高16位中不存在数据为1的比特位
根据结果设置计数标记
cnt_16 = flag<<4;
根据上述提到的可得答案
/* 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 flag;
int cnt_16,cnt_8,cnt_4,cnt_2,cnt_1,cnt_0;
int sign = x>>31;
x = (sign&(~x))|(~sign&(x));
flag = !!(x>>16);
cnt_16 = flag<<4;
x = x>>cnt_16;
//b16 = !!(x>>16)<<4;
//x = x>>b16;
flag = !!(x>>8);
cnt_8 = flag<<3;
x = x>>cnt_8;
flag = !!(x>>4);
cnt_4 = flag<<2;
x = x>>cnt_4;
flag = !!(x>>2);
cnt_2 = flag<<1;
x = x>>cnt_2;
flag = !!(x>>1);
cnt_1 = flag;
x = x>>cnt_1;
cnt_0 = x;
return cnt_16 + cnt_8 + cnt_4 + cnt_2 + cnt_1 + cnt_0 + 1;
}
求浮点数乘以2的结果
⚠️这里需要注意的是函数传入的参数以及返回值都是无符号整型数
也就是说变量uf虽然是一个无符号整型数,但在题目中我们需要把它的二进制表示解析成一个单精度浮点数
忘记浮点数表示的同学可以看一下这张图,也可以再看一遍书加深一下记忆
看了图之后我们可以思考一下如何将一个无符号整型数解析成单精度浮点数呢?
A:根据单精度浮点数的定义来划分变量uf的32个bit位
其中uf二进制表示的最高位(31位)表示的是符号位s
第23~30位表示阶码字段(exp)
第0~22位表示小数字段(frac)
我们需要从uf中提取符号字段、阶码字段和小数字段
那么获取阶码字段exp将uf与0x7F800000(如下)按位“与运算”,再将结果右移23位即可
0 1111 1111(exp) 0000.....0000
即 exp = (0x7F800000 & uf ) >>23
这样就解析出阶码字段exp,获取其他字段的方式同理,大家可以自己动手试一试
由此,我们将无符号整型数解析成单精度浮点数
根据IEEE浮点规则我们知道
V = ((-1)^ s ) * M * ( 2 ^ E )
接下来我们根据阶码exp的值分类讨论
1.当exp = 0xFF时,表示特殊值,当为特殊值时我们知道有两种情况。一种(当frac不等于0)表示不是一个数(NaN),另一种(frac等于0)表示无穷(根据符号位表示正无穷还是负无穷)。
当为NaN时根据题目要求,直接返回uf即可
当为无穷时,无穷 * 2 结果还是无穷,返回uf
2.当exp = 0时,表示非规格化的数,由于非规格化的数表示数值0或者非常接近0的数
当frac = 0 时,此时表示0 ,0乘以任何数都为0,所以直接返回uf(注意当符号位不同时,正零与负零是有区别的,但由于0怎么乘都为0,所以这里我们不做讨论,直接返回uf,不能返回零)
当frac != 0 时,此时表示非常接近0的数,针对这情况只需将小数字段乘以2即可(左移一位)
3.当exp 既不等于0,也不等于0xFF时,此时表示规格化的数
对于这种情况乘以2只需要对 exp + 1 即可
(具体原因可以看上面的IEEE浮点表示规则)
不过这里还有一种特殊情况要出处理,当exp = 254,此时虽然是一个规格化的数,但阶码字段加1之后会超出规格化所能表示的范围,针对这种情况,我们需要返回无穷(无穷分为正无穷和负无穷,这里根据符号位判断即可)
最终用或操作和移位操作将这三个字段拼成一个32bit的数返回即可
具体代码如下
//float
/*
* 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) {
unsigned s = uf & 0x80000000;
unsigned exp = (uf & 0x7F800000)>>23;
unsigned frac = uf & 0x7FFFFF;
unsigned ret = 0;
unsigned inf = s | (0xFF << 23);
if(exp == 0xFF)return uf;
if(exp == 0)
{
if(frac == 0)return uf;
frac = frac << 1;
ret = s| (exp<<23) | frac;
return ret;
}
exp++;
if(exp == 0xFF)return inf;
ret = s | (exp<<23) | frac;
return ret;
}
把单精度浮点数强制转化成整型数
根据IEEE浮点规则(上题提到)可以发现
1.当E等于0时,此时表示的数不是整数,所以只需返回0即可
由于浮点数所能表示的范围远大于整型数(32位整型数只能表示 -2^31 ~ 2^31 -1 )
当E = 31, s = 1, V = -2^31
E = 31, s = 0, V = 2^31 此时浮点数表示的数超过了整型所能表示的最大值
2.综上,当E >31 时 浮点数所表示的数值超过了整型数能表示的最大值,此时函数返回0x80000000u即可
3.E在 0 ~ 31 之间如何转换
此时根据exp的值可以知道,数据为规格化的数
由于E的取值范围在0 ~ 31,而小数字段的长度是23位,所以E的范围又要分成两种情况讨论
0<= E <= 23 对小数字段进行截断处理(如果E = 23,直接返回小数字段,E= 22,舍弃小数字段最后一位-->小数字段右移一位)
24 <= E <= 31 E = 24(小数字段左移一位),E = 25(左移两位)
最后看符号位,若为负数则取反加一,正数直接返回即可
代码如下
/*
* 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) {
unsigned sign = uf>>31;
unsigned exp = (uf & 0x7F800000) >>23;
unsigned frac = uf & 0x7FFFFF;
int result = 0;
int E = exp - 127;
frac = frac | 0x00900000;
if(E < 0) return 0;
if(E > 31) return 0x80000000u;
if(E >23) result = frac<<(E - 23);
else result = frac >> (23 - E);
if(sign == 1)
return (~result) + 1;
else
return result;
}
返回与表达式2.0^x等效的位级别
说人话就是要用单精度浮点数表示规则表示2的x次方
根据y = 2^x 函数图像可以看出,y随x增大而增大
当y的值太小时,函数返回0即可
如果y太大,则返回正无穷
那么当x取何值才能导致y的值太大或太小呢?
x = -149 时,是单精度浮点数能表示最接近0的数,此时返回0(x<-149相同)
x > 127 时,会超过单精度浮点数所能表示的最大范围,此时返回正无穷
用非规格化数表示趋近于零的数,其他用规格化的数
规格化数表示的最小值是2的-126次方(E = exp - bias)
可以得到规格化数与非规格化数的分界线(-127)
-149 < x <= -127 时,表示y的值需要用非规格化的数
x == -149 frac: 0x1 << 0
x == -148 frac: ox1 << 1
-126 <=x <=127 时,y用规格化的数
x == -126 exp = 1
x == -125 exp = 2
代码如下
/*
* 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) {
int exp;
unsigned ret;
if(x < -149) return 0; //值小返回0
if(x > 127) return (0xFF << 23);//值大返回无穷
//非规格化数最接近0 exp = 0 , f = 0000 ... 0000 0001
//E = 1-bias = -126 M = f = 2 ^ -23 x == -149
//exp = 254, E = exp - 127, Emax = 127 x>127 返回正无穷
//有一部分接近零,要用非规表示
//exp = 1, E = exp - bias(127) = -126 Vmin = -126 -147 ~ -127用非规
//-126 ~ 127 规格化的数
if(x < -126) return 0x1 << (x + 149);
exp = x + 127;
ret = exp << 23;
return ret;
}