CSAPP-数据表示与运算实验

目录

一、实验目的

二、实验要求及注意事项

三、实验原理与内容

1.位操作

2.补码运算

3.浮点数操作

四、实验设备与软件环境

五、实验过程与结果

1、操作符及运算概览

(1)位运算和逻辑运算

(2)补码运算

(3)浮点数

2、功能实现与结论

(1)isZero

(2)Negate

(3)specialBits

(4)upperBits

(5)bitMatch

(6)bitOr

(7)absVal

(8)logicalNeg

(9)bitParity

(10)byteSwap

(11)getByte

(12)isGreater

(13)isNegative

(14)isPower2

(15)addOK

(16)subtractionOK

(17)oddBits

(18)replaceByte

(19)rotateLeft

(20)floatAbsVal

(21)floatIsEqual

3、代码检验


一、实验目的

1. 更好地熟悉和掌握计算机中整数和浮点数的二进制编码表示。

2. 加深对数据二进制编码表示的了解。

3. 使用有限类型和数量的运算操作实现一组给定功能的函数。

、实验要求及注意事项

1. 注意给出的可以使用的运算符及最大个数。

2. 除了float类型的两个操作函数可以使用if-else条件语句外,其它函数只能用顺序结构进行代码设计和实现。

3. 本实验使用btest程序进行评分时记分为50分制,但最后教师端进行实际成绩核算时会使用100分制(乘以2)。

三、实验原理与内容

本实验每位学生拿到一个datalab-handout.tar文件。学生可以通过U盘、网盘、虚拟机共享文件等方式将其导入到Unbuntu实验环境中,选择合适位置存放。然后在Ubuntu环境下解压。解压后,根据文件中的叙述和要求更改bits.c文件,其他文件不要动。本次实验的主要操作方式为:使用C语言的位操作符实现题目要求。

完成实验后提交给老师时,将自己编写的bits.c改名为“bits_学号.c”的形式交给学委统一收集。文件名例:bits_18210320101.c。如果文件名出现错误,则自动评分程序不会给出分数,请务必注意!!!!

需要完成bits.c中下列函数功能,具体分为三大类:位操作、补码运算和浮点数操作。

1.位操作

表1列出了bits.c中一组操作和测试位组的函数。其中,“级别”栏指出各函数的难度等级(对应于该函数的实验分值),“功能”栏给出函数应实现的输出(即功能),“约束条件”栏指出你的函数实现必须满足的编码规则(具体请查看bits.c中相应函数注释),“最多操作符数量”指出你的函数实现中允许使用的操作符的最大数量。

也可参考tests.c中对应的测试函数来了解所需实现的功能,但是注意这些测试函数并不满足目标函数必须遵循的编码约束条件,只能用做关于目标函数正确行为的参考。

表1 位操作题目列表

本题分数

函数名

功能

约束条件

最多操作符数

1

isZero

判断变量x是否为0。如果为0,则返回1;否则,返回0。

仅可以使用以下操作符: !  ~  &  ^  |  + <<  >>

2

1

specialBits

构建0xffca3fff,并返回。

仅可以使用以下操作符: !  ~  &  ^  |  + <<  >>

3

1

upperBits

根据输入的变量n,构建一个高n位为1其他位为0的数,并返回该值。

注:0<= n <=32

仅可以使用以下操作符: !  ~  &  ^  |  + <<  >>

10

1

bitMatch

构建一个比特序列(int型),构成规则如下:如果x和y在某一个bit位置的值相同,则此序列的相应位置为1,否则该位置值为0。

仅可以使用以下操作符: ~ & |

14

1

bitOr

计算按比特或(x | y),并将计算结果返回。

仅可以使用以下操作符: ~ &

8

4

logicalNeg

使用位操作符实现逻辑非(!x)操作,并将取逻辑非之后的结果返回。

仅可以使用以下操作符: ~  &  ^  |  +  <<  >>

12

4

bitParity

如果x中包含奇数个0,则返回1;否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

20

2

byteSwap

将x的第n字节和第m字节交换,

0 <= n <= 3, 

0 <= m <= 3,

然后将交换后的值返回。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

25

2

getByte

提取x的第n个字节。0 <= n <= 3 (0代表最最低为字节,3代表最高位字节),并将其返回。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

6

2

oddBits

返回一个32bit数,这数的所有第奇数个bit位置的值为1。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

8

3

replaceByte

将x的第n个字节用 c 进行替换,

0 <= n <= 3, 0 <= c <= 255

并将替换后的结果返回。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

10

3

rotateLeft

将x向左循环移位n个bit。循环移位是指左边移除去的比特自动填充到右边空出的位置上。 0 <= n <= 31,并将循环移位后的值返回。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

25

2.补码运算

表2列出了bits.c中一组使用整数的补码表示的函数。可参考bits.c中注释说明和tests.c中对应的测试函数了解其更多具体信息。

表2 补码运算题目列表

本题分数

函数名

功能

约束条件

最多操作符数

2

negate

将输入参数x的值取相反数,返回-x。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

5

4

absVal

计算变量x的绝对值,并将其绝对值返回。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

10

3

isGreater

如果x > y,则返回1,否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

24

2

isNegative

如果x < 0,返回1;否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

6

4

isPower2

如果x是2的整数次幂,则返回1;否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

20

3

addOK

如果x+y没有溢出,则返回1;否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

20

3

subtractionOK

如果x-y没有溢出,则返回1;否则返回0。

仅可以使用以下操作符: !  ~  &  ^  |  +  <<  >>

20

3.浮点数操作

表3列出了bits.c中一组浮点数二进制表示的操作函数。可参考bits.c中注释说明和tests.c中对应的测试函数了解其更多具体信息。注意输入参数和返回结果均为unsigned int类型,但应作为单精度浮点数解释其32 bit二进制表示对应的值。

表3 浮点数操作题目列表

本题分数

函数名

功能

约束条件

最多操作符数

2

floatAbsVal

通过bit级操作返回一个float型浮点数的绝对值。如果输入参数为NaN,则直接返回输入参数的原值。

可以使用任何的操作符,包括||和&&。也可以使用if,while。

10

2

floatIsEqual

判断两个浮点数是否相等,如果相等则返回1,否则返回0。

如果输入参数中含有NaN,则返回0。

注:+0和-0被当作相等的情况对待。

可以使用任何的操作符,包括||和&&。也可以使用if,while。

25

四、实验设备与软件环境

1. Linux操作系统—64位 Ubuntu 18.04

2. C编译环境(gcc)

3. 计算机

五、实验过程与结果

1、操作符及运算概览

(1)位运算和逻辑运算

符号

描述

运算规则

&

两个位都为1时,结果才为1

|

两个为都为0时,结果才为0

^

异或

两个位相同为0,相异为1

~

取反

0变1,1变0

<<

左移

各二进位全部左移若干位,高位丢弃,低位补0

>>

右移

各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

!

单目运算,将真值变为假(0),假变为真(1)

+

加法

前后值相加

||

短路或

前后值全false时,计算结果为false,否则为true,且具备短路功能,即第一个操作数false则不计算第二个操作数

&&

短路与

前后值全true时,结算结果为true,否则为false,与短路或同样具备短路功能

(2)补码运算

a. 机器数和真值

在学习原码,反码和补码之前,需要先了解机器数和真值的概念。一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1。比如,十进制中的数+3,计算机字长为8位,转换成二进制就是00000011。如果是-3,就是10000011。

那么,这里的 00000011 和 10000011 就是机器数

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值

b. 原码、反码和补码

现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:

[+1] = [00000001]原 = [00000001]反 = [00000001]补

所以不需要过多解释. 但是对于负数:

[-1] = [10000001]原 = [11111110]反 = [11111111]补

可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?

① 计算机中符号位参与运算

首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了。于是人们开始探索将符号位参与运算, 并且只保留加法的方法。首先来看原码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.

为了解决原码做减法的问题, 出现了反码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

② 补码运算

于是补码的出现, 解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)

使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值。

(3)浮点数

图1 浮点数的表示

浮点数的表示基于IEEE 754标准,其表示如上图1所示。符号位s为0时,则表示为数值的绝对值。特殊值NaN为非数值,是指当exp全为1,也称无定义数。

2、功能实现与结论

结合实验原理与内容,对文件bits.c按照要求进行更改,完成三大类功能:位操作、补码运算和浮点数操作。基于具体功能实现要求,对文件bits.c进行如下更改,代码及详解如下所示:

(1)isZero

代码:

int isZero(int x) {
    return !x;
}

解析:

判断变量是否为0,结果非1即0,考虑使用逻辑非“!”来返回结果,实现当变量为0时,返回1,否则返回0。

(2)Negate

代码:

int negate(int x) {
	//计算机用补码运算 
	return ~x+1;
}

解析:

为了返回相反数,考虑使用补码运算,即将x取反后加1就可以得到相反数。

(3)specialBits

代码:

int specialBits(void) {

/*

f     f    c     a    3    f     f    f

1111 1111 1100 1010 0011 1111 1111 1111

0000 0000 0011 0101 1100 0000 0000 0000

0000 0000 0000 0000 0000 0000 1101 0111

==128+64+16+4+2+1=215

*/

    return ~(215<<14);//~(0XD7<<14)

}

解析:

基于题目要求,考虑先进行逆向拆解,首先0xffca3fff它对应的二进制数为:1111 1111 1100 1010 0011 1111 1111 1111,发现其只有中间部分较为特殊,高10位和低14位均为1,考虑使用取反求得特殊值为1101 0111(取反后),通过移位来进行运算,得到题目所求构建数。即将1101 0111(215)向左移14位,再取反就可以完成要求。

(4)upperBits

代码:

int upperBits(int n) {

    return ((!!n)<<31)>>(n+(~0));

}

解析:

为了得到高n为为1其它位为0的二进制数,思路考虑为构建最高位为1然后算术右移n-1位进行构建。但考虑到n=0时,它的值直接为0,所以不能直接通过1进行移位,则使用逻辑非“!”运算:!!n—当n=0,!!n=0;否则!!n=1。由于没有减法运算,右移n-1位即可用n+(~0)来表示。

n+(~0)==n-1

(5)bitMatch

代码:

int bitMatch(int x, int y) {

    return (x&y)|(~x&~y);

}

解析:

考虑到离散数学中的德摩根定律:两数相交取反等于两数取反相并。类比至位运算中,基于x和y某一bit位置相同值输出为1,否则为0,得出只有四种可能:00、01、10、11。而x&y仅能实现11的对应可能,基于德摩根定律将两数取反,即与运算上~x和~y,可以实现00的对应可能并输出为1,再将两个结果进行或运算得到要求构建的比特序列(int型)

(6)bitOr

代码:

int bitOr(int x, int y) {

    return ~(~x&~y);

}

解析:

同样基于德摩根定律,对两数取反再与运算再次取反,可以得到或运算结果。

(7)absVal

代码:

int absVal(int x) {

    return (x^(x>>31))+((x>>31)&1);

}

解析:

正数和零的绝对值为本身,负数的绝对值为取反加一,符合补码运算原理。使用算术右移基于最高位1/0在左边补1/0,则x>>31来区分正负数,并与原x进行异或运算,如果x为正数则保留,为负数则取反;考虑加一:负数的绝对值需要考虑加一,但正数不需要,则依靠x>>31区分并与1进行与运算(正数算术右移31位全为0,则与1与运算为0,即不加1;负数反之)

(8)logicalNeg

代码:

int logicalNeg(int x) {

    return ((x|(~x+1))>>31)+1;

}

解析:

逻辑非中,基于~x+1为x的相反数,考虑使用算术右移31位来区分0和非0。对x的本身及相反数进行或运算:x=0时结果为0;x!=0时高位为1。最后+1使用溢出来区分0/1。

(9)bitParity

代码:

int bitParity(int x) {

    x^=x>>16;

    x^=x>>8;

    x^=x>>4;

    x^=x>>2;

    x^=x>>1;

    return x&1;

}

解析:

x中包含奇数个1和奇数个0是同等含义。将各bit上的数值进行异或运算,奇数个1时返回1,否则返回0,利用这一特点,求解各bit上数值的异或运算结果并与1进行与运算,可得题解。考虑相折,进行移位运算对高16位于低16位进行异或运算,得到的结果再分成高8位和低8位并继续异或运算直到得出最低位的数值,即为各bit位置上的数循环异或的结果。若设置8bit数值,将各bit位标号为1-8,可预览效果为:                   

原序列12345678
>>40/10/10/10/11234
异或1^52^63^74^8
>>21^52^6
异或1^3^5^72^4^6^8
>>11^3^5^7
异或1^3^5^7^2^4^6^8

(10)byteSwap

代码:

int byteSwap(int x, int n, int m) {

    int a = (x>>(n<<3))&0xff;   

    int b = (x>>(m<<3))&0xff;   

    int am = a<<(m<<3);

    int bn = b<<(n<<3);

    int mask = x&~((0xff<<(n<<3)|0xff<<(m<<3)));

    return bn+am+mask;

}

解析:

为了交换不同字节,先把n、m处的字节提取出来成a、b。n<<3=n*8,是因为1字节=8位,要定位到n字节就*8。&0xff只取低8位;再把a、b扩位到它要放入的位成am、bn;接着把x处的n、m字节重置为0,利用y+(~y+1)(补码)=0的性质,成bm、an,bm&an即x处的n、m字节重置为0。最后把它们结合起来成bn+am+(an&bm),即完成。

(11)getByte

代码:

int getByte(int x, int n) {

    return (x>>(n<<3))&0xff;

}

解析:

1个字节为8个比特,所以n<<3相当于n*8,然后将x向右移那么多位,再同0xff进行与运算保留低八位的值,即可得第n个字节的值。

(12)isGreater

代码:

int isGreater(int x, int y) {

    int sign = ((~x&y)>>31)&1;

    int mark = ~((x^y)>>31);

    int equl = !!(x^y);

    return sign|((mark)&(~(x+~y+1))>>31&equl);

}

解析:

当x>y时,若x为正数,y为负数,结论则成立;所以需要考虑两种情况,一种是x为正数,y为负数时直接返回1,反之就要满足x和y同号且值不相等,并且x-y值符号为0(值为正数),同时保证x和y不相等。即:

sign检验是否x为正数,y为负数,是则返回1,否则返回0;

mark检验x和y是否异号,异号返回0,同号则返回1;

equl检验x和y是否相等,相等则返回0,否则返回1;

(13)isNegative

代码:

int isNegative(int x) {

    return ((x>>31)&1);

}

解析:

为了得到负数返回1,其他返回0,将x向右移31位,符号位就位于末尾了,负数的符号位为1,非负为0,则再和1进行与运算保存最低位就可以得到了结果了。

(14)isPower2

代码:

int isPower2(int x) {

    return (!(x>>31)&!(x&(x+(~0))))&!!x;

}

解析:

每个数在二进制中都是唯一的。也就是2的幂,在二进制中只有一位是1其他全是0才有可能是2的幂(除了第一位是1不是2的幂)。即可以用x&(x-1)来判断,如果只有一个1返回0,加个逻辑非“!”运算:!(x&(x+(~0));还要考虑是不是负数,是负数就返回0:!(x>>31);最后判断是不是0,是0也要返回计算:!!x。

(15)addOK

代码:

int addOK(int x, int y) {

    return !((!(x>>31^y>>31))&(x>>31^(x+y)>>31));

}

解析:

发现条件可以概括为:x和y符号位相同,且x和sum符号位不同时,发生了溢出。

(16)subtractionOK

代码:

int subtractionOK(int x, int y) {

    return !((x>>31^y>>31)&(x>>31^(x+~y+1)>>31));

}

解析:

不同于addOK,将x-y处理为x+~y+1即可。条件为:x和y符号位不同,且x和sum符号位不同时,发生了溢出。

(17)oddBits

代码:

int oddBits(void) {

    return (0xaa)+(0xaa<<8)+(0xaa<<16)+(0xaa<<24);

}

解析:

构造0xaaaaaaaa。

(18)replaceByte

代码:

int replaceByte(int x, int n, int c) {

    return (c<<(n<<3))+(x&~(0xff<<(n<<3)));

}

解析:

将数据c和0xff算术左移n*8位,并且使用构建的新0xff数与x进行与运算,顺利把x的第n个字节清空,最后加上移位后的c,得到题解。

(19)rotateLeft

代码:

int rotateLeft(int x, int n) {

    return (x<<n)|((x>>(32+~n+1))&(~0+(1<<n)));

}

解析:

先得到左移n位后的数:x<<n,考虑循环被移位的数据时,即留存被循环的高位,则先构造一个高32-n位为0,低n位都为1的特殊数:~0+(1<<n);为了保留高n个bit的值,则将x向右移动32-n位,把前n位移到最低位并与前面的特殊数进行与运算,成功保留。再跟x<<n进行或运算,得到题解。

(20)floatAbsVal

代码:

unsigned floatAbsVal(unsigned uf) {

  int x=uf&0x7fffffff;

  if(x>0x7f800000)

    return uf;

  else

    return x;

}

解析:

基于IEEE浮点表示法,最高位为符号位s,负数s=1,正数s=0,只要把符号位变为0,即可取绝对值,即x=uf&0x7fffffff。NaN表示的是指阶码全为1,小数域不全为0的数,经过上述符号转变后为0x7f8。即当x大于0x7f8说明x为NaN。

(21)floatIsEqual

代码:

int floatIsEqual(unsigned uf, unsigned ug) {

    if(!(uf&0x7fffffff)&&!(ug&0x7fffffff))

return 1;

    if((uf&0x7fffffff)>0x7f800000)

return 0;

    if((ug&0x7fffffff)>0x7f800000)

return 0;

    return uf==ug;

}

解析:

首先判断是否都为0,都为0(+-0)则相等,return 1;再判断uf或ug绝对值是否大于NaN,可基于floatAbsVal中来构造if。如果三个条件都不满足,再来用==判断两个数是否相等,得到题解。

3、代码检验

(1)使用dlc检查函数实现代码是否符合实验要求的编码规则。

首先./dlc bits.c直接检测是否有错误。如图2所示:

图2

由图知,如果没有任何错误输出,则表示bits.c文件的编写无误,基本符合要求。

然后用-e选项调用dlc,观察操作符数。如下图3所示。通过此选项可以查看每一道具体题目是否符合要求。如果没有任何问题,输出结果如下图1.2所示。如果局部出现问题,则输出会类似于图4所示,在具体位置表示出相应题目的具体问题。对于本例,小题1:语法错误,小题3:超过了最多操作符数量要求,原题要求不超过3个,结果使用了5个。

正在上传…重新上传取消正在上传…重新上传取消

图3 无问题情况

图4 有问题的情况

(2)使用 btest 检查函数实现代码的功能正确性。

首先使用make编译生成btest可执行程序,如图5所示。部分warning不需要特殊处理,但如果出现的warning过多则需要适当注意是否程序中有错误。

图5

然后调用 btest 命令检查 bits.c中所有函数的功能正确性。如图6所示,得分会以数字形式显示在左侧,错误显示则显示error。

图6

  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林北发量惊人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值