计算机系统实验——datalab
完整代码:
链接:https://pan.baidu.com/s/1W74CDNnetD8YNbbqXAlSqw
提取码:dzwe
目录
第一步,阅读datalab中的相关资料(README、bits.c),明确实验要求;
实验题目:LAB2-datalab | |
实验目的:通过此次实验,进一步熟悉整型及浮点数的位表达形式,实现常用二进制运算的常用方法。 | |
实验环境:个人电脑、linux发行版本 | |
实验内容及操作步骤:一、实验准备,在所使用的电脑平台上安装VMWare tools(开启文件夹共享,以便接下来的实验);
安装过程不断回车,直至安装完成
【虚拟机设置】-【选项】-【共享文件夹】中添加共享文件夹,将LAB-datalab所在文件夹共享到虚拟机。
二、开始实验第一步,阅读datalab中的相关资料(README、bits.c),明确实验要求;我们需要补充bit.c中的函数,所有的工作都只需修改 bits.c 文件,然后通过btest, dlc和 BDD checker来测试代码。
任务指引主要有以下一些说明:
第二步,一个个的bits.c中补充缺失的函数并测试
根据德摩根律,,可以得到最简单的表达式:
编号是从LSB(Least Significant Bit,最低有效位为0)到MSB(Most Significant Bit,最高有效位为3)。 利用按位与&,1&X=X,0&X=0的特性,只要把要取的第n个字节移到最低位后和0xff按位与,第n个字节会保留,编号大于n的字节会被清零。因为1个字节是8位,因此取第n个字节时要先将其右移8*n位,8*n用位运算可表示为(n<<3);
因为c语言里的>>运算符默认是算术右移(算术右移符号位补位,逻辑右移0补位),所以可以先进行算术右移,再将补位的符号位(可能是0或1)换成0; 假设要逻辑右移n位,就需要将最高位的n位符号位全部替换成0,那么就可以利用左边n位为0,右边32-n位为1的二进制码与算术右移n位后的原码按位与&,便可得到逻辑右移n位后的目标数。 构造这个二进制码可以用:1 << 31得到100...,算术右移n位,得到1...10...0,共n+1位为1。再左移1位,即可得到 1...10...0,共n位为1,再将这个数取反即可。(当然也有其他构造方法) 实现:
采用二分法,自底向上,先计算x每两位中1的个数,由count对应两位存储。以此类推,计算每4位、8位、16位中1的个数,最后的整合结果即为x中的1的个数。每次“错位”相当于一次相加。
原理分析: 以1101 0010为例 实现:
也就是判断该数的二进制位是否全为0,若全为0则返回1,否则返回0 只有当x=0时,~x为全1,~x+1为全0(逸出),x|(~x+1)的最高位为0。 只要x不为0,x与 ~x+1的最高位一定有一个为1,因此,~(x|(~x+1))即为结果。只需要将x|(~x+1)右移31位,与1按位与,再按位取非即可。 实现:
即为最高位为1,其余各位为0。最高位为符号位,负数,再减小一位即为正数,因此是最小的整数。 实现:
将x先左移shift,再右移shift,其实是保留了低n位,其余各位置0。shift的值实际上是32-n,利用了补码原理。 将x与该值按位异或,只有当x的第31位~第n位全为0时,按位异或的结果才为0;否则便为1。因此,只需要将按位异或的结果取非,即可得知x是否超过了n位二进制所能表示的范围。 -4、5的原理:
实现:不允许使用减,因此用~x+1表示负数
如果x为整数,直接x>>n即为正确结果,但由于代码不能使用if语句,因此需要考虑其他方法。 对于负数,除法是向0取整,而右移位是向负取整,因此需要加上偏置量2^n-1(低n-1位全为1,其余各位全为0)。通过x>>31得到符号(全1或全0),与2^n-1按位与即可得到偏置量。x加上偏置量,向右移n位。(正数则不加偏置量)
注意:右移实现除法中的偏置量问题: 在计算机中,如果两个int型数a和b作除法(a/b),当a不能被b整除的时候,表达式的结果为(a/b)的商。这个商是通过向下取整得来的。即对于17/16,结果在区间(1,2)上,向下取整,得到1。考虑a<0的情况,当a=-17时,结果在(-2,-1)上,向下取整,得到-2。而我们期望的结果是-1。通过与偏置量相加,将结果偏移到(-1,0)上,再向下取整,就得到-1。 当不能整除时,计算机都会采取向下取整运算。对于正数,向下取整是符合我们习惯的,对于负数,我们的期望时绝对值向下取整,即整体向上取整,这与计算机的向下取整不符合,所以就要通过偏置量来实现。 关于偏置量的取值,当a>=0时,偏置量值为0;当a<0时,设偏移量为n,偏置量取值为(2^n-1)。
实现: 用~0表示-1;
按位取反加一即可 实现:
正数符号位为0,负数符号位为1,向右移31位得到全0(正)或全1(负),再与1相与后得到0(正数)、1(负数),再取反得到正确结果;
实现:
测试结果: 用了3个操作符; btest测试错误,因为测试0的时候应该返回0,但是我的程序返回了1; 原因:0的符号位也是0,忽略了x>0返回1的条件,使0也返回了1;
修改程序: 当x为正数时,x>>31为全0,!((x>>31)|(!x))即为!!x,x不为0时结果为1。 x为负数时,x>>31为全1,(x>>31)|(!x)为全1,!((x>>31)|(!x))结果为0。
测试结果: 用了4个操作符 结果正确;
需要判断x-y的符号位,但是如果x正、y负或x负y正则可能越界,因此不能直接判断,分为几种情况:
因此将上面的三个式子相或后得到正解; 实现: 测试结果: 18个操作符,满足条件; 中间出现过许多次错误,主要原因是情况的划分不正确,以及0、1与、或之间有些混乱,参考了网上代码后终于修正;
我们要找的就是最高位的那个1,并且返回它的位置; 可以使用二分法,先在左16位找: 如果有1则在左边16位的前8位中找,如果没1则在右边16位中的前8位找(丢弃左16位),一直划分下去直到仅剩1位,那一位就是我们要找的最高位1; 注:x>0,算数右移补0; 用shift表示丢掉的位数,如果丢掉高位则shift为0,如果丢掉低位则是丢掉的位数(为了求最高位1的位置);
实现:
测试: 27个操作符,满足条件; 测试正确;
与第二章作业中的2_92相似; 参数和返回结果都是无符号整数,但是可以解释成单精度浮点数的二进制表示。 根据IEEE 754标准:阶码全1,尾数非全0的表示无效数NaN。 判断f是否为NaN,是则直接返回,否则将符号位取反;
实现: 完全用2_92的代码:
测试结果: 7个操作符
符号位(sign): 看最高有效位即可; 尾数: int型(二进制表示)可假设小数点在最低有效位右边,循环左移n-1次,使得第一个有效数字1在最高有效位,小数点在其右边,可以计算小数点移动了32-n位),那么小数点右 边第1位(首位)到第23位就是浮点数表示的尾数部分了,因为这里只要取23位所以存在精度问题,这里采取四舍五入向偶数舍入的方式; 阶码部分: 看小数点移动的位数,加上偏置量(float中为127)即可。即(127+32-n)<<23。 0x01ff为111111111,0x0100为100000000, 判断是否需要进位,如果需要,flag为1
实现:
测试结果: 17个操作符,满足条件 结果正确;
分为几种请况:
实现: 测试: 9个操作符
实验结果及分析:1、dlc测试:
所有操作符的数量都符合限制; 2、btest测试: 所有函数功能正确; 从上述dlc与btest测试中可以看出,15个函数均满足操作符号数限制以及功能测试,成功编写并通过了bits.c中的15个函数空缺部分;
收获与体会:通过本次实验,我更进一步的掌握了浮点数、整数的二进制表示,初步理解并学会了位级操作,学会了利用普通的位级操作符来实现一些简单的和一些较复杂的操作,如取反、int转换为float等,在填充这些函数时,我受益匪浅。 在设计这些函数的过程中,将位级操作与算法设计融合起来,在二进制层面上设计算法更进一步加深了我对位级操作的理解和掌握,提供了设计算法、实现问题的新的基于位级操作的思想。 在实验过程中出现过很多次错误,包括函数的设计思想的错误,在网上查询资料后最终解决,这个过程中也学到了很多。 |