深入理解计算机系统 csapp datalab 详解
bitXor
通过逻辑与,逻辑非实现异或功能
//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 a = 1;
int b = 0;
int c = (~(a&b)) & (~((~a)&(~b))); // 这也是一种方式
System.out.println(c);
return ~((~(~x&y))&(~(x&~y)));
}
/*使用离散数学的方法,列出真值表,得到
xor = (~x&y)|(x&~y)
再使用德摩根律消去或即可
allOddBits
所有的奇数位都为 1 吗
/*
* 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
*
思路:先构造出一个全部奇数位为1,偶数位为0的数字detect用于检测
将x|detect,若x全部奇数位都为1,那么使用或操作不会使tmp和x有差别,否则tmp!=x
最后,检测tmp和x是否相等即可
*/
private static int allOddBits(int x){
int tmp;
int detect = 0xAA << 8 | 0xAA;
detect = detect << 16 | detect;
tmp = x | detect; // 如果x的奇数位都是1,那么tmp就和x是相等的。
return (tmp ^ x) == 0 ? 1 : 0;
}
isAsciiDigit
题目要求如下所示,让你判断一个数的十六进制是不是 [0x30,0x39] 的区间范围之内
/*
* 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
*
首先取出x的最低8位,作为xt
xt的高4位,作为xh,低4位作为xl
smaller用来判断9是否<smaller,若是,则取1
最终的条件是,x>0(通过xt^x判断),xh==3(通过xh^0x3判断),以及xl<=9
*/
private static int isAsciiDigit(int x) {
int xt = x&0xff; // x的最低8位
int xh = xt>>>4; // xt的高4位 >>>逻辑右移,右边补0
int xl = xt&0x0f;
int isEight = (xt^x) == 0 ? 1:0; // 判断x是否只有8位bit
int isThree = (xh^0x3) == 0 ? 1:0; // 判断高4位是否为3;
/*
* ~xl+1(xl取反,加1,表示xl的负数) 逻辑右移31位,判断是否为负数
* */
int smaller = ( (0x9+(~xl+1)) >>>31)&0x1;
smaller = (smaller == 0) ? 1:0; // xl<=9,smaller为1.否则smaller为0
//(xh^0x3) == 0 ? 1 : 0;
//return !(xt^x)&!(xh^0x3)&(smaller);
return isEight & isThree & smaller;
}
conditional(x, y, z)
使用位级运算实现C语言中的 x?y:z三目运算符。又是位级运算的一个使用技巧。
/*
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
c语言版本
*/
int conditional(int x, int y, int z) {
x = !!x; // 如果x为0, 则 !!x为0. 如果x不为0,则 !!x为1;
x = ~x + 1; // 如果x为1, 则~x + 1后,就是 0xFFFFFFFF(即-1). 如果x为0,则运算后依旧为0.
return (x & y) | (~x & z);
}
/*
* x !=0 ? y : z; 即x不等于0,返回y;否则返回z
* Java版本
*/
public static int conditional(int x, int y, int z) {
// 怎么根据x构造出一个数 x!=0, 构造出 0xFFFFFFFF. x==0,构造出0x0;
x = ((x|(~x+1))>>31);
// int x = !!x;
// x = ~x + 1;
return (x&y) | (~x&z);
}
logicalNeg(x)
使用位级运算求逻辑非 !
/*
逻辑非就是非0为1,非非0为0。利用其补码(取反加一)的性质,
除了0和最小数(符号位为1,其余为0),外其他数都是互为相反数关系(符号位取位或为1)。
0和最小数的补码是本身,不过0的符号位与其补码符号位位或为0,
最小数的符号位为1。利用这一点得到解决方法。
*/
public static int logicalNeg(int x){
return ((x|(~x+1))>>31)+1;
}
tmin()
返回最小的补码
public static int tmin(){
return 1<<31;
}
isTmax
判断是否是补码的最大值。32位补码的最大值为0x7fffffff,与其异或
public static int isTmax(int x){
// 补码最大值 0x7FFF FFFF
int t = x^0x7FFFFFFF;
x = ((t|(~t+1))>>31)+1;
return x;
}
negate
不用负号得到 -x
// 不用负号得到 -x
// 补码实际上是一个阿贝尔群,对于x,-x是其补码,所以-x可以通过对x取反加1得到
public static int negate(int x){
return (~x+1);
}
isLessOrEqual
判断x是否小于等于y.
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
* 判断x ≤ y 是否成立
*/
public static int isLessOrEqual(int x, int y){
int dValue = y + ~x + 1; //y-x
int dSign = dValue >> 31 & 1; //y-x符号
int sign = 1 << 31;
int xSign = ((x & sign) >> 31) & 1; // 取x最高位
int ySign = ((y & sign) >> 31) & 1; // 取y最高位
/*
* x>0,y>0 bitXor为0
* x<0,y<0 bitXor为0
*
* x>0,y<0 bitXor为1
* x<0,y>0 bitXor为1
*/
int bitXor = xSign ^ ySign;
int nbitXor = ((bitXor |(~bitXor+1))>>31)+1; // !bitXor
int ndSign = ((dSign |(~dSign+1))>>31)+1; // !dSign
// return (bitXor & xSign) | ((!bitXor) & (!dSign));
return (bitXor & xSign) | ((nbitXor) & (ndSign));
}
howManyBits
题目要求:return the minimum number of bits required to represent x in two’s complement。
例如: howManyBits (12) = 5
howManyBits (298) = 10
howManyBits (-5) = 4
howManyBits (0) = 1
howManyBits (-1) = 1
howManyBits (0x80000000) = 32
允许操作:! ~ & ^ | + << >>
该题要求我们返回该数字用补码表示时最少能用几位来表示 我们可以用二分法来判断有多少位。
然后我们还要对特殊情况 0 和 - 1 进行区分对待。
值得一提的是,我们对负数要取反,但不必加一,因为补码表示中负数范围是比正数大的,而最小的负数取反后刚刚好是最大的正数。
public static int howManyBits(int x){
int s,c1,c2,c3,c4,c5,c6;
int cnt = 0; // 计数
s = (x>>31)&1; // 符号位 正数就是0,负数就是1
x = ((s<<31)>>31) ^ x; // 取反x 如果x为正数,则异或后,x仍为原来的值。否则x按位取反。
int tmp = x>>16;
tmp = ((tmp | (~tmp+1))>>32)+1;
s = ((tmp | (~tmp+1))>>32)+1;
//s = !!(x>>16); // 判断高16位是否有1,有则s为1
c1 = s<<4; // 若高16位有1,则低16位可以计数16
x >>= c1; // 改变x的值 右移将已经计数的位移除,c1若为0,则用折半的长度判断
//s = !!(x>>8); // 用8位的长度去判断,有效位的个数计入c2
tmp = x>>8;
tmp = ((tmp | (~tmp+1))>>32)+1;
s = ((tmp | (~tmp+1))>>32)+1;
c2 = s<<3;
x >>= c2;
//s = !!(x>>4); // 用4位的长度去判断,有效位的个数计入c3
tmp = x>>4; // x剩下8个bit位
tmp = ((tmp | (~tmp+1))>>32)+1;
s = ((tmp | (~tmp+1))>>32)+1;
c3 = s<<2;
x >>= c3; // 再右移4位
//s = !!(x>>2); // 用2位的长度去判断,有效位的个数计入c4
tmp = x>>2; // x剩下4个bit位置
tmp = ((tmp | (~tmp+1))>>32)+1;
s = ((tmp | (~tmp+1))>>32)+1;
c4 = s<<1;
x >>= c4; // x右移2位,剩下2个bit位
//s = !!(x>>1); // 用1位的长度去判断,有效位的个数计入c5
tmp = x>>1;
tmp = ((tmp | (~tmp+1))>>32)+1;
s = ((tmp | (~tmp+1))>>32)+1;
c5 = s;
x >>= c5; // 右移1位, 剩下1个bit位
//c6 = !!x; // 判断最低位是否为1
tmp = x;
tmp = ((tmp | (~tmp+1))>>32)+1;
c6 = ((tmp | (~tmp+1))>>32)+1;
cnt = c1+c2+c3+c4+c5+c6+1; // 将每次获得的低位有效位相加,再加1位符号位
return cnt;
}
float_twice
/*
实验目标:求2uf,uf是一个用unsigned表示的float,当遇到NaN时返回该NaN
*允许的操作符:任意操作符包括 && || if while
*最大操作符数: 30
*难度:★★★★
*/
如果阶码为0,那么就是非规格数,直接将尾数左移1位到阶码域上,其他不变即可。例如 0 00000000 1000…001 变成 0 00000001 000…0010。
这样可以做的原因正是由于非规格化数的指数E = 1 - bias,而不是-bias。这样使得可以平滑地从非规格数过度到规格化数。
如果阶码不为0且不是255,那么直接阶码加1即可。 如果阶码为255,那么是NaN,∞,-∞,直接返回即可。
考察浮点数编码的问题。浮点数有规格化、非规格化、无穷和NaN。
无穷和NaN,乘2也是返回uf本身。
再分类讨论规格化和非规格化。 非规格化 == 阶码域全 0 ,所以保留符号位,再将frac 左移一位即可,相当于乘2的一次幂。 若 uf 为规格化,则将 uf 的指数位加 1。
#include<stdlib.h>
#include<stdio.h>
unsigned float_twice(unsigned uf) {
if( (uf & 0x7F800000) == 0 ) // 非规格数
uf = ((uf & 0x007FFFFF) << 1) | (0x80000000 & uf);
else if (( uf & 0x7F800000) != 0x7F800000)
uf = uf + 0x800000; // 若 uf 为规格化,则将 uf 的指数位加 1
return uf;
}
unsigned float_twice2(unsigned uf) {
unsigned signX, expPart, fracPart;
unsigned helper = 1 << 31;
unsigned fracMask = (1 << 23) - 1;
if (0 == uf) { // positive 0
return 0;
}
if (helper == uf) { // negative 0
return helper;
}
signX = uf & helper;
expPart = (uf >> 23) & 0xff;
if (expPart == 0xff) {
return uf;
}
fracPart = uf & fracMask;
if (0 == expPart) { // 非规格化值
fracPart = fracPart << 1;
if (fracPart & (1 << 23)) {
fracPart = fracPart & fracMask;
expPart += 1;
}
} else {
expPart += 1;
}
return signX | (expPart << 23) | fracPart;
}
int main(){
printf("hello world!!!\n");
//unsigned uf = 0x400000;
unsigned uf = 0x7F000000;
unsigned uf2 = uf;
uf = float_twice(uf);
printf("%d\n",uf);
uf2=float_twice2(uf2);
printf("%d\n",uf2);
return 0;
}
float_i2f(x)
与上一题相同,分三部分处理,获取符号位s_ = x&0x80000000,若为负数-x,变为正数,则0x80000000为特殊情况分开处理,考虑特殊情况,0x0和0x80000000,这两种情况直接返回0和0xcf000000。
获取最高位的1所在位置,while(!(x&(1<<n_))) n_–;。
若n_ <= 23这个数需要向左移动到小数部分起始位置(将1放在第23位上),if(n_<=23) x<<=(23-n_);。
若n_ > 23这个数需要向右移动到小数部分起始位置(将1放在第23位上),这时候需要考虑移出部分的舍入问题,若移出部分大于0.5则向上舍入,若小于0.5则向下舍去,若等于0.5则向偶数舍入。
先将>=0.5情况等同考虑,向上舍入x+=(1<<(n_-24))。若==0.5时,舍入情况若为奇数,我们需要-1操作变为偶数,即将最低位的1变为0,x&=(0xffffffff<<(n_-22)),若向上舍入时最高位产生了进位,还需要加上进位if(x&(1<<n_)) ;else n_++;。之后拼接浮点数即可。
将补码转化为浮点数编码步骤:
将补码转化为无符号数,并根据补码的符号来设置浮点数的符号位
因为补码一定是大于等于0的数,所以要么为0,要么为规格化数。如果是规格化数,首先统计除了最高有效位外一共需要几位,得到的就是E,然后通过 得到解码位为 。
无符号数后面E位就是尾数部分,但是需要判断该部分是否23位,如果小于23位,直接将其左移填充;如果大于23位,需要对其进行舍入:
如果是中间值,就需要向偶数舍入
如果不是中间值,就需要向最近的进行舍入.
#include<stdlib.h>
#include<stdio.h>
typedef unsigned char *byte_pointer;
/*
* 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 E = 0; //阶数
unsigned frac=0;
unsigned round=0; //舍入误差
unsigned sign = x & (1<<31); //取出符号位
unsigned absX = sign ? (~x+1) : x; //求绝对值
unsigned temp = absX;
while((temp = temp>>1)) //求阶数
++E; //因为整型数所以E一定是大于等于0的
frac = absX << (31-E)<<1; //因为默认第一位为1,所以舍去第一位的1
round = frac <<23>>23; //求的被舍去部分
frac = frac >> 9; //求的尾数
if(round > 0x100) round = 1; //大于一半 加1
else if(round < 0x100) round =0; //小于一半 舍弃
else round = frac & 1; //等于一半 取最近的偶数
printf("E=%d\n",E);
printf("frac=%d\n",frac);
printf("round=%d\n",round);
//x为0时,其浮点型以为0 || exp=E+bias (bias=127 单精度)
return x ? (sign | ((E+0x7F)<<23) | frac) +round: 0;
}
unsigned floatInt2Float(int x) {
int s_ = x&0x80000000;
int n_ = 30;
if(!x) return 0;
if(x==0x80000000) return 0xcf000000;
if(s_) x = ~x+1;
while(!(x&(1<<n_))) n_--;
if(n_<=23) x<<=(23-n_);
else{
x+=(1<<(n_-24));
if(x<<(55-n_)) ;else x&=(0xffffffff<<(n_-22));
if(x&(1<<n_)) ;else n_++;
x >>= (n_-23);
}
x=x&0x007fffff;
n_=(n_+127)<<23;
return x|n_|s_;
}
void show_bytes(byte_pointer start, int len) {
int i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]); //line:data:show_bytes_printf
printf("\n");
}
void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int)); //line:data:show_bytes_amp1
}
void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float)); //line:data:show_bytes_amp2
}
int main(){
printf("hello world!!!\n");
int num = 5;
unsigned uf = float_i2f(num);
float numf = uf;
printf("%d,%f\n",uf, numf);
show_int(uf);
show_float(numf);
uf = floatInt2Float(num);
printf("%d,%f\n",uf, numf);
int a = 5;
float b = a;
printf("%d,%f\n",a, b);
show_int(a);
show_float(b);
return 0;
}
float_f2i
题目要求: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.
允许操作:Any integer/unsigned operations incl. ||, &&. also if, while
这个也是考察基本的对于 IEEE 浮点数格式的转换了,思路也比较清晰,就是根据不同的部分来求出对应的值
#include<stdlib.h>
#include<stdio.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, int len) {
int i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]); //line:data:show_bytes_printf
printf("\n");
}
void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int)); //line:data:show_bytes_amp1
}
void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float)); //line:data:show_bytes_amp2
}
int float_f2i(unsigned uf) {
int exp = (uf>>23) & 0xFF; // 阶乘 8位bit
int frac = uf & 0x007FFFFF; // 23bit的小数部分
int sign = uf & 0x80000000;
int bias = exp - 127;
if(exp == 255 || bias > 30){
// 阶乘255,就是NaN。 指数是2的30次方,对于整数就太大了。
return 0x80000000u;
}else if(!exp || bias<0){
// exp如果为0, 就是非规格数, 阶乘就是-126. 对于整数来说,太小了。
return 0;
}
// append a 1 to the front to normalize
frac = frac | 1 << 23; // 拼接前缀的1
printf("frac=%d\n",frac);
// float based on the bias
if (bias > 23) {
frac = frac << (bias - 23);
} else {
frac = frac >> (23 - bias);
}
printf("exp=%d\n",exp);
printf("bias=%d\n",bias);
printf("frac=%d\n",frac);
if (sign) {
// original number was negative, make the new number negative
frac = ~(frac) + 1;
}
return frac;
}
int main(){
printf("hello world!!!\n");
int num = 5;
unsigned uf = 0;
float numf = uf;
uf = 0x40a00000;
numf = uf;
uf = float_f2i(0x40a00000);
printf("%d,%f\n",uf, numf);
return 0;
}