深入了解计算机系统-实验二APP Data Lab-较详细解释

计算机系统实验二APP Data Lab

实验题目:APP Data Lab
实验目的:我的目标是修改bits.c文档,完成所有函数的编写,利用dlc和btest两个工具通过所有的测试

实验环境:Ubuntu12.04环境
实验内容及操作步骤:
1.bitAnd函数
a.函数要求

  • bitAnd - x&y using only ~ and |
  • Example: bitAnd(6, 5) = 4
  • Legal ops: ~ |
  • Max ops: 8
  • Rating: 1
    b.函数功能
    能够实现并返回两个数与操作之后的结果
    c.设计思想
    由于能够使用的运算符只有~ |,所以我们不能够直接使用与操作符。联系已学知识,可以使用德摩根定律,将x&y转化为((x)|(~y))
    d.代码实现
int bitAnd(int x, int y) {
  return ~((~x)|(~y));
}

2.getByte函数
a.函数要求

  • getByte - Extract byte n from word x
  • Bytes numbered from 0 (LSB) to 3 (MSB)
  • Examples: getByte(0x12345678,1) = 0x56
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 6
  • Rating: 2
    b.函数功能
    取出一个int数据指定的字节对应的数并以16进制表示出来
    c.设计思想
    题目的要求就是提取处x的第n个字节,对于以上的例子,78第0字节,56第一字节,以此类推即可,所以这里提出的是0x56。那么这里的思路就是能够让x向又移动8*n个bits即可,单位我们需要提取
    转换后的低八位,那么可以用他和(0…0)11111111进行与操作,因为1&x=x,因此得到的就是对应的位
    d.代码实现
int getByte(int x, int n) {
    n = n << 3;	
    x = x >> n;
    x = x & 255;
    return x;
}

3.logicalShift函数
a.函数要求

  • logicalShift - shift x to the right by n, using a logical shift
  • Can assume that 0 <= n <= 31
  • Examples: logicalShift(0x87654321,4) = 0x08765432
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 20
  • Rating: 3
    b.函数功能
    能够正确的实现逻辑位移
    c.设计思想
    在学习的过程中,我们接触过两种不同的位移方式,一种是算数位移,一种是逻辑位移,这两种在左移的情况下没有什么区别,但在右移的时候逻辑位移都是用0来补充,但算数位移是需要考虑符号位的。所以思路上要考虑将逻辑位移后的前n为进行置零操作,其余保持不变,那么我们可以将位移后的结果和0x(00…0)11…1进行与操作,其中恰好有n个0。那么现在主要的问题就是如何得到0x(00…0)11…1所以为了得到(00…0)11…1可以先(11…1)00…0然后取非,(11…1)00…0可以由100…0进行右移n位得到。
    d.代码实现
int logicalShift(int x, int n) {
    int temp = ((1 << 31) >> n) << 1;//由于不能使用减号
    temp = ~temp;
    return (temp & (x >> n));
}

4.bitCount函数
a.函数要求

  • bitCount - returns count of number of 1’s in word
  • Examples: bitCount(5) = 2, bitCount(7) = 3
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 40
  • Rating: 4
    b.函数功能
    计算一个二进制数中的1的个数
    c.设计思想
    基于数字电路的设计思想或者迭代思想,即构建一个“具有多层次结构的加法器”,实质上就是先统计一位上的1的个数,然后将结果相加,这样我们就得到两位的统计结果,接着将两位的统计结果相加,那样就可以得到四位的统计结果…由此迭代下去最终的课都达到统计32位1的个数的效果。以下是一个具体的例子:
    在这里插入图片描述

所以现在需要解决的问题是:怎样去统计结果呢?事实上,我们可以利用掩码,比如有掩码01010101,那么与一字节相与,那么就可以统计偶数位上的1的个数,此时如果我们将原数右移一位,这样就可以达到将刚才的奇数位移动到偶数位,继续同样操作,就达到统计奇数位的效果,最后将这个结果累加…同理之后也是如此,但掩码变成00110011,00001111,其实都是一样的。
d.代码实现

int bitCount(int x) {

    int count;
    
    int tmpMask1 = (0x55)|(0x55<<8);
    int mask1 = (tmpMask1)|(tmpMask1<<16);  //得到掩码: 01010101……01010101
    
    int tmpMask2 = (0x33)|(0x33<<8);
    int mask2 = (tmpMask2)|(tmpMask2<<16);  //得到掩码: 00110011……00110011
    
    int tmpMask3 = (0x0f)|(0x0f<<8);
    int mask3 = (tmpMask3)|(tmpMask3<<16);  //得到掩码: 00001111……00001111
    
    int mask4 = (0xff)|(0xff<<16);          //得到掩码: 0000 0000 1111 1111 0000 0000 1111 1111
    
    int mask5 = (0xff)|(0xff<<8);           //得到掩码: 0000 0000 0000 0000 1111 1111 1111 1111
    
    count = (x&mask1)+((x>>1)&mask1);       //分别计算每组2位中,低位的1的个数;再移位求每组2位中,高位的1的个数;求和
    count = (count&mask2)+((count>>2)&mask2);   //两两相加
    count = (count + (count >> 4)) & mask3;     //同理,两两相加
    count = (count + (count >> 8)) & mask4;     //同理,两两相加
    count = (count + (count >> 16)) & mask5;    //同理,两两相加
    return count;
}

5.bang函数
a.函数要求

  • bang - Compute !x without using !
  • Examples: bang(3) = 0, bang(0) = 1
  • Legal ops: ~ & ^ | + << >>
  • Max ops: 12
  • Rating: 4
    b.函数功能
    逻辑非操作
    c.设计思路
    只有当x=0时,x为全1,x+1为全0(逸出),x|(~x+1)的最高位为0。只要x不为0,x与 ~x+1的最高位一定有一个为1,话句话说其实就是除了0以外的所有数,它和它的相反数的符号位必然是不一样的。
    d.代码实现
int bang(int x) {
	int temp = x | (~x + 1);
	temp = ~(temp >> 31);
	return (temp & 1); 
}

6.tmin函数
a.函数要求

  • tmin - return minimum two’s complement integer
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 4
  • Rating: 1
    b.函数功能
    返回二进制最小整形数据
    c.设计思路
    了解二进制补码的实质和二进制补码的计算规则,在了解二进制补码计算规则的基础上,我们可以知道最小的二进制补码其实就是10…0,所以我们只需要将1进行左移就可以实现这个函数的功能
    d.代码实现
int tmin(void) {
	int result = 1<<31;
	return result;
}

7.fitsBits函数
a.函数要求

  • fitsBits - return 1 if x can be represented as an
  • n-bit, two’s complement integer.
  • 1 <= n <= 32
  • Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 15
  • Rating: 2
    b.函数功能
    判断x能否由n位二进制补码表示
    c.设计思路
    判断x能否由n位二进制补码表示,首先这个数可以被32位表示,将一个数左移32-n然后右移动32-n,可以知道,这个n位实际上是被保留下来的,在移位过程中实际上是把高于n的位全部覆盖了,因此会导致得到的值和之前的不同,所以我们只需要把变换后的数和原始数据进行异或,观察返回值是不是全1即可,或者也可以先取反,观察结果是否为0
    d.代码实现
int fitsBits(int x, int n) {
	int shiftnumber=32+~n+1;//32-n
        int xx=x<<shiftnumber>>shiftnumber;
        return !(xx^x);//和原来相同返回1,表示能用n位表示 
}

8.divpwr2函数
a.函数要求

  • divpwr2 - Compute x/(2^n), for 0 <= n <= 30
  • Round toward zero
  • Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 15
  • Rating: 2
    b.函数功能
    计算x/(2^n)的值并且向0取整
    c.设计思想
    我们最先想到的思路其实就是移位,这种除法只需要将x向右移位n位即可。但是,由于题目是要求需要向0取整,其实这个就是类似于二进制补码的除法。通过对正常右移的结果和向0取整结果在数轴表示出来,可以明显发现,在正数时结果是相同的,但到了负数的区间,两者之间就有一个明显的且一致的偏置值,这个值就是2^n-1,所以依照这个思想,我们只需要在右移时,加上这个偏置值即可
    d.代码实现
int divpwr2(int x, int n) {
	int sign = x >> 31;
	int mask = (1 << n) + (~0);
	return  (x + (sign & mask)) >> n;
}

9.negate函数
a.函数要求

  • negate - return -x
  • Example: negate(1) = -1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 5
  • Rating: 2
    b.函数功能
    计算相反数
    c.设计思路
    补码操作,取反加一
    d.代码实现
int negate(int x) {
  return (~x) + 1;
}

10.isPositive函数
a.函数要求

  • isPositive - return 1 if x > 0, return 0 otherwise
  • Example: isPositive(-1) = 0.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 8
  • Rating: 3
    b.函数功能
    判断一个数是否是正数返回1,否则返回0
    c.设计思路
    一个整数无非就三种情况,大于0,等于0,小于0。对于正数和负数,显然我们只需要观察它们的符号位即可,而对于等于0的数,就只需要再加一个另外的判断条件即可
    d.代码实现
int isPositive(int x) {
  return !((x>>31)|(!x));
}

11.isLessOrEqual函数
a.函数要求

  • isLessOrEqual - if x <= y then return 1, else return 0
  • Example: isLessOrEqual(4,5) = 1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 24
  • Rating: 3
    b.函数功能
    判断x<=y,是则返回1否则返回0
    c.设计思路
    首先想到的是可不可以从高位直接一位一位的进行比较,答案是太麻烦了。通过转换,其实我们可以观察x-y的结果来判断进行输出。如果结果的符号位是1或者结果是0,那么显然x<=y,这个时候输出1。但真是这样吗?并不是,因为在计算机中会出现一个非常严重的问题,就是溢出,对于这里也一样会出现。对于加法同号是会溢出的,减法相反异号会溢出。因此同号的时候只需要观察x-y的符号位,如果异号,就意味着x是正数y为负数,其实只需要输出x的符号位即可
    d.代码实现
int isLessOrEqual(int x, int y) {
  	int signx=(x>>31)&1;//x的符号位 
  	int signy=(y>>31)&1;//y的符号位 
  	int signy_x=((y+~x+1)>>31)&1;//y-x的符号位 
  	return (!(signx^signy)&!signy_x) | ((signx^signy)&signx);//同号 异号 
}

12.ilog函数
a.函数要求

  • ilog2 - return floor(log base 2 of x), where x > 0
  • Example: ilog2(16) = 4
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 90
  • Rating: 4
    b.函数功能
    计算x对2的对数并且向下取整
    c.设计思路
    返回log(x),这个返回的其实是x对2的对数并且向下取整,这也就意味着,只关注最高位的一个1即可。那么关键问题就是如何得到这个最高位1的位置,换个角度讲,我们可以统计最高位1前面0的个数,因为最高位之前所有的位必定位0(因为log不会出现负数)。因此思路如下,首先看前16位是不是全为0,不是则看前16位前8位,是则看后16位的前8位…以此类推
    d.代码实现
int ilog2(int x) {
  int logs=(!!(x>>16))<<4;
  logs=logs+((!!(x>>(logs+8)))<<3);
  logs=logs+((!!(x>>(logs+4)))<<2);
  logs=logs+((!!(x>>(logs+2)))<<1);
  logs=logs+(!!(x>>(logs+1)));  
  return logs;
}

13.float_neg函数
a.函数要求

  • float_neg - Return bit-level equivalent of expression -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 representations of
  • single-precision floating point values.
  • When argument is NaN, return argument.
  • Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
  • Max ops: 10
  • Rating: 2
    b.函数功能
    返回一个unsigned表示浮点数的负数
    c.设计思路
    功能比较简单,只需要把符号位给取一个反,可以用异或实现。但要注意的是,对于nan我们需要直接进行输出,不然会出现错误。
    d.代码实现
unsigned float_neg(unsigned uf) {
  unsigned int a=0xff;
  unsigned int b=1<<31;
  unsigned int exp=(uf>>23)&a;//取出阶码
  unsigned int frac=uf&0x7fffff;//尾数
  if(!(exp^a)&&frac) return uf;//当nan,返回uf否则返回-uf
  return uf^b;
}

14.float_i2f函数
a.函数要求

  • 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
    b.函数功能
    返回一个int数据强制转换位浮点数的unsigned类型表示
    c.设计思路
    浮点数由三个部分组成,按无符号正数相加即可。按照《深入了解计算机系统》课本中,我们首先要得到这个int数的位数,同时得到符号位,阶码和尾数,对于负数,需要正数化,对于阶码要判断是否是nan。其中最重要的就是尾数,它可能会存在一个截尾,这就需要四舍六入五向偶。具体操作就是最低位下一位加一个1,试想一下,如果最低位下一位如果为0,该操作无进位,最后我们进行一个减1操作,为的就是区分五向偶(进位后需要判断和处理变成nan的情况)。
    d.代码实现
unsigned float_i2f(int x) {
    if(!x) return 0;//为0返回
    else{
    int exp=0;
    unsigned temp1=x;
    unsigned temp2,temp3;
    int shift;
    int sign=0x80000000&x;//取出符号位
    if (sign) temp1 = ~temp1 + 1;//如果是负数取它的相反数
    temp3=temp2=temp1;
    //以下是计算x的位数
    while(temp1){
        exp=exp+1;
        temp1=temp1>>1;
    }
    exp = exp + ~1 + 1;// 减一操作,由于exp得到的是x的位数,但是在转换过程中我们取得是位数,举个例子:10011011->1.0011011*2^7,我们要尾数的位数即exp-1
    shift=23-exp;//计算要移位的长度
    if(shift>=0)  temp3=temp3<<shift;//shift为正,表示可以用23位表示
    else//否则发生截取,我们需要右移,并且要考虑舍入
    {
        shift=-shift;
        temp2=temp2>>shift;//右移shift位
        temp3+=(1<<(shift-1));//无论怎样,我们都加上低位的进位
        if(!(temp2&1)) temp3+=-1;//如果最低位不为1,说明无法发生偶数进位,所以减去1。如果四舍五入,则不会借位成功舍入,否则不发生进位。试着带入一个例子:对于(1.001)10,(1.001)11,(1.010)11,(1.010)10,(1.001)01
        temp3=temp3>>shift;//右移shift
    }
    exp=((exp+0x7f)<<23)+(temp3>>1);//加上temp3可能发生向exp的进位
    exp=exp&0xff800000; //清零frac
    temp3=temp3&0x7fffff;//取后23位
    return sign|exp|temp3;
    }//合并
}

15.float_twice函数
a.函数要求

  • float_twice - 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
    b.函数功能
    返回一个unsigned表示的浮点数2的结果
    c.设计思路
    对于一个浮点数
    2的结果,需要对非规格化数和规格化数进行区分,其中非规格化数是左移,规格化是直接阶码+1(要判断是否会变成nan,如果会进一步处理)。为什么非规格化数是直接移位了,对于非规格化数它×2以后很有可能还是非规格化数,而非规格化数的阶码一定是八个0,意味着我们只能对尾数移位,不然会把一个非规格化数转化为规格化数(数值也不一样了),除非一个规格化数×2之后超过非规格化数表示范围,也就是产生了进位,这时候阶码加一,对于规格化数的×2就直接改阶码,但也要注意是否会让规格化数变成一个nan
    d.代码实现
unsigned float_twice(unsigned uf) {
    unsigned int s=uf&0x80000000;
    unsigned int exp=uf&0x7f800000;
    unsigned int frac=uf&0x7fffff;
    if(exp==0x7f800000)//Nan and inf
    {
    return uf;
    }
    else if(!exp)//非规格化数
    {
    frac<<=1;
    }
    else//规格化数,直接exp+1,同时如果exp全1那么需要对frac清零防止nan
    {
    exp+=(1<<23);
    if(exp==0x7f800000) frac=0;
    }
    return (s|exp|frac);
}	

实验结果及分析:
实验结果:
1.输入命令 ./dlc bits.c以及./dlc -e bits.c查看语法错误并检查操作符消耗

在这里插入图片描述

2.编译并执行程序,输入命令make btest以及./btest执行
在这里插入图片描述

结果分析:
经过第一步的操作之后,我们可以看到没一个函数使用的操作符资源消耗,它们分别为4,3,6,33,6,1,7,7,2,4,16,27,8,30,11,可以判断都满足试验的要求。之后通过第二步的操作,可以观察到各项得分分别是1,2,3,4,4,1,2,2,2,3,3,4,2,4,4,最后总得分是41,程序都满足实验要求。
收获与体会:
这次实验真是让我感到了计算机系统下的支配,对于有些题目自己有思路,但往往是不能够在要求内给实现的,不能用if不能用while,又不能用其他啥啥的,反正就整的相当难受,一道题能够卡那里半天,有许多的题目承认自己是通过搜索资料整理的,之前是直接莽代码,思路都没看,结果发现肝不动。这15道题,做了两遍,第一遍算是照个面吧,看了看其他人的代码和思路,马马虎虎的把代码给“模仿了一遍”。但我知道这肯定是不够的,自己必然没有理解,于是自己手写,真的手写把所有题目的思考过程重新一遍手写了一遍,于是就把所有题目给肝透了。从初次接触到完全理解确实花了不少功夫,在其中是深刻意识到计算机的一些基础操作比如异或,与,或等等操作符的实用性,之前也很少用,并且其中有个非常重要的问题,就是所有的操作基本上都是通过位来实现的,移位实现除法,掩码辅助来实现计算等等都非常实用。
几个值得思考的问题?我们是否能够通过这次实验真正了解计算机内部的工作机制,在前一个实验我们了解了汇编,这次实验我们用位操作来实现一些基础功能,所以我们是否能够建立起联系?可以看到,其中有些函数是用到了许多的操作符的,我们能否进行优化,进一步减少操作符的使用,实质上就是有没有更好的算法?最后就是问问自己,是否真正的理解了非规格化数和规格化数以及它们之间的区别,这很重要!

https://blog.csdn.net/weixin_44307065/article/details/105497452
https://blog.csdn.net/qq_40889820/article/details/88831962

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 好的,这里是您要求的回答: 这个问题涉及到计算机系统实验课程——深入理解计算机系统(CS:APP)的第一个lab任务,也称为Data Lab。这个任务要求学生使用C语言编写函数来实现不同的位级操作,从而深入理解计算机中数字的存储和运算。 ### 回答2: 在Data Lab的第部分中,我们学习了如何用不超过十六个操作来实现不同的位运算。这些操作包括移位,逻辑运算,比较和掩码等。通过这些操作的实现,我们可以进一步理解底层计算机系统中位运算的实现原理。 在实现这些操作时,我们需要了解底层计算机系统的运算和数据类型。如符号扩展、零扩展和反码等。同时,在编写代码时需要熟练使用位运算的操作符号以及一些基本控制流语句如循环、条件语句等。 除了实现这些基本操作,我们也需要应用这些操作来解决一些实际问题。例如,实现一个函数,将一个十六进制数按位翻转,或是计算一个整数进制表示中1的数量等。 通过Data Lab的学习,我们深入了解计算机系统中底层的位运算实现原理,并学会了如何用简洁高效的代码实现这些操作。同时,这些操作也常常被用在各种领域的计算机编程中,对于未来的学习与工作都有很大的帮助。 ### 回答3: 在《深入理解计算机系统lab1:data lab)中,我们主要学习了位运算和两个的补码表示。这些知识对于我们了解计算机的原理和编码方式非常重要。 在这个实验中,我们需要完成一些与位运算相关的任务。其中包括: 1. 实现位运算。我们需要用 C 语言实现一些常见的位运算,如与、或、非、异或、左移、右移等。 2. 计算 x 的相反数。 3. 检查 x 是否为零。 4. 判断 x 的符号是否为负数。 5. 计算 x 和 y 的和,但不能使用加法运算。 为了完成这些任务,我们需要对 C 语言数据类型的细节和位运算的机器级实现有一定的了解。例如,我们要了解有符号整数和无符号整数的区别,以及它们在机器上的表示方式。我们还需要理解位运算的计算过程,包括位移的规则、符号扩展和逻辑运算等。 通过这个实验,我们可以更深入地理解计算机的工作原理,学会用底层的方式实现高级的运算,掌握常用的位运算技巧。这对于提高编程的效率和代码的可读性都有很大帮助。同时,这也为后续的计算机科学学习打下了坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

call me Patrick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值