深入理解计算机系统 csapp datalab 详解(位操作,数据表示)

深入理解计算机系统 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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值