HNU-计算机系统(CSAPP)实验二 DataLab

一、实验题目及要求

实验指导书在README文件中,中文翻译版如下:

***********************
CS:APP 数据实验室
学生指南
***********************

您的目标是修改您的 bits.c 副本,使其在不违反任何编码指南的情况下通过 btest 中的所有测试。
测试,同时不违反任何编码指南。


*********
0. 文件:
*********

Makefile - 制作 btest、fshow 和 ishow
README - 此文件
bits.c - 您将修改并提交的文件
bits.h - 头文件
btest.c - 主 btest 程序
 btest.h - 用于构建 btest
 decl.c - 用于构建 btest
 tests.c - 用于构建 btest
 tests-header.c - 用于构建 btest
dlc* - 规则检查编译器二进制文件(数据实验室编译器)	 
driver.pl* - 使用 btest 和 dlc 自动分级 bits.c 的驱动程序
Driverhdrs.pm - 用于可选 "击败教授 "竞赛的头文件
fshow.c - 用于检查浮点表示法的实用程序
ishow.c - 用于检查整数表示法的实用程序

***********************************************************
1. 修改 bits.c,检查其是否符合 dlc 标准
***********************************************************

重要: 开始之前,请仔细阅读 bits.c 文件中的说明。
中的说明。这些说明给出了编码规则,如果您想获得全额奖励,就必须遵守这些规则。
如果您想获得完整的信用。

使用 dlc 编译器 (./dlc) 自动检查您的
bits.c 的版本是否符合编码指南:

 unix> ./dlc bits.c

如果代码没有问题,dlc 会无声返回。
否则,它会打印信息,标出任何问题。使用
开关运行 dlc:

 	unix> ./dlc -e bits.c 

会导致 dlc 打印每个函数使用的运算符数量。

一旦有了合法的解决方案,就可以使用
测试其正确性。

*********************
2. 使用 btest 测试
*********************

该目录中的 Makefile 会将您的 bits.c 版本与
附加代码,以创建名为 btest 的程序(或测试线束)。

要编译并运行 btest 程序,请键入

 unix> make btest
 unix> ./btest [可选 cmd 行参数]

每次更改 bits.c
程序时,都需要重新编译 btest。从一个平台转移到另一个平台时,您需要
删除旧版本的 btest 并生成新版本。使用
命令:

 unix> make clean
 unix> make btest

Btest 通过在每个函数上运行数百万个测试用例来测试代码的正确性。
每个函数的测试用例。它可以测试众所周知的角
情况,如整数谜题的 Tmin 和 0,以及 0、inf 和
浮点数谜题中的去规范化数和规范化数之间的边界。
和浮点数的去规范化与规范化之间的边界。当 btest 检测到某个函数出错时,它会打印出测试失败的结果、
会打印出失败的测试、错误的结果和预期的结果,然后终止测试。
预期结果,然后终止对该函数的测试。

以下是 btest 的命令行选项:

 unix> ./btest -h
 使用方法: ./btest [-hg] [-r <n>] [-f <name> [-1|-2|-3 <val>]*] [-T ]。[-T <时间限制>] [-T <时间限制
 -1 <val> 指定第一个函数参数
 -2 <val> 指定第二个函数参数
 -3 <val> 指定第三个函数参数
 -f <name> 仅测试指定函数
 -g 自动交易输出格式,无错误信息
 -h 打印此信息
 -r <n> 对所有问题赋予 n 的统一权重
 -T <lim> 设置超时限制为 lim

示例

 测试所有函数的正确性并打印错误信息:
 unix> ./btest

 以简洁的形式测试所有函数,不显示错误信息:
 unix> ./btest -g

 测试函数 foo 的正确性:
 unix> ./btest -f foo

 使用特定参数测试函数 foo 的正确性:
 unix> ./btest -f foo -1 27 -2 0xf

Btest 不会检查您的代码是否符合编码准则。
准则。请使用 dlc 进行检查。

*******************
3. 辅助程序
*******************

我们提供了 ishow 和 fshow 程序,分别帮助您破解整数和浮点表示法。
整数和浮点表示。每个程序都以一个
十进制或十六进制数字作为参数。要编译它们,请键入

 unix> make

使用示例

 unix> ./ishow 0x27
 十六进制 = 0x00000027,有符号 = 39,无符号 = 39

 unix> ./ishow 27
 十六进制 = 0x0000001b,有符号 = 27,无符号 = 27

 unix> ./fshow 0x15213243
 浮点数值 3.255334057e-26
 位表示法 0x15213243,符号 = 0,指数 = 0x2a,分数 = 0x213243
 归一化。+1.2593463659 X 2^(-85)

 linux> ./fshow 15213243
 浮点数值 2.131829405e-38
 位表示法 0x00e822bb,符号 = 0,指数 = 0x01,分数 = 0x6822bb
 归一化。+1.8135598898 X 2^(-126)

通过DeepL可以轻松完成翻译 ʕง•ᴥ•ʔง ʕ•ᴥ•ʔ ʕ ᵔᴥᵔ ʔ

bits.c文件中要求如下:

/* 
 * CS:APP Data Lab 
 * 
 * <Please put your name and userid here>
 * 
 * bits.c - Source file with your solutions to the Lab.
 *          This is the file you will hand in to your instructor.
 *
 * WARNING: Do not include the <stdio.h> header; it confuses the dlc
 * compiler. You can still use printf for debugging without including
 * <stdio.h>, although you might get a compiler warning. In general,
 * it's not good practice to ignore compiler warnings, but in this
 * case it's OK.  
 */

#if 0
/*
 * Instructions to Students:
 *
 * STEP 1: Read the following instructions carefully.
 */

You will provide your solution to the Data Lab by
editing the collection of functions in this source file.

INTEGER CODING RULES:
 
  Replace the "return" statement in each function with one
  or more lines of C code that implements the function. Your code 
  must conform to the following style:
 
  int Funct(arg1, arg2, ...) {
      /* brief description of how your implementation works */
      int var1 = Expr1;
      ...
      int varM = ExprM;

      varJ = ExprJ;
      ...
      varN = ExprN;
      return ExprR;
  }

  Each "Expr" is an expression using ONLY the following:
  1. Integer constants 0 through 255 (0xFF), inclusive. You are
      not allowed to use big constants such as 0xffffffff.
  2. Function arguments and local variables (no global variables).
  3. Unary integer operations ! ~
  4. Binary integer operations & ^ | + << >>
    
  Some of the problems restrict the set of allowed operators even further.
  Each "Expr" may consist of multiple operators. You are not restricted to
  one operator per line.

  You are expressly forbidden to:
  1. Use any control constructs such as if, do, while, for, switch, etc.
  2. Define or use any macros.
  3. Define any additional functions in this file.
  4. Call any functions.
  5. Use any other operations, such as &&, ||, -, or ?:
  6. Use any form of casting.
  7. Use any data type other than int.  This implies that you
     cannot use arrays, structs, or unions.

 
  You may assume that your machine:
  1. Uses 2s complement, 32-bit representations of integers.
  2. Performs right shifts arithmetically.
  3. Has unpredictable behavior when shifting an integer by more
     than the word size.

EXAMPLES OF ACCEPTABLE CODING STYLE:
  /*
   * pow2plus1 - returns 2^x + 1, where 0 <= x <= 31
   */
  int pow2plus1(int x) {
     /* exploit ability of shifts to compute powers of 2 */
     return (1 << x) + 1;
  }

  /*
   * pow2plus4 - returns 2^x + 4, where 0 <= x <= 31
   */
  int pow2plus4(int x) {
     /* exploit ability of shifts to compute powers of 2 */
     int result = (1 << x);
     result += 4;
     return result;
  }

FLOATING POINT CODING RULES

For the problems that require you to implent floating-point operations,
the coding rules are less strict.  You are allowed to use looping and
conditional control.  You are allowed to use both ints and unsigneds.
You can use arbitrary integer and unsigned constants.

You are expressly forbidden to:
  1. Define or use any macros.
  2. Define any additional functions in this file.
  3. Call any functions.
  4. Use any form of casting.
  5. Use any data type other than int or unsigned.  This means that you
     cannot use arrays, structs, or unions.
  6. Use any floating point data types, operations, or constants.


NOTES:
  1. Use the dlc (data lab checker) compiler (described in the handout) to 
     check the legality of your solutions.
  2. Each function has a maximum number of operators (! ~ & ^ | + << >>)
     that you are allowed to use for your implementation of the function. 
     The max operator count is checked by dlc. Note that '=' is not 
     counted; you may use as many of these as you want without penalty.
  3. Use the btest test harness to check your functions for correctness.
  4. Use the BDD checker to formally verify your functions
  5. The maximum number of ops for each function is given in the
     header comment for each function. If there are any inconsistencies 
     between the maximum ops in the writeup and in this file, consider
     this file the authoritative source.

/*
 * STEP 2: Modify the following functions according the coding rules.
 * 
 *   IMPORTANT. TO AVOID GRADING SURPRISES:
 *   1. Use the dlc compiler to check that your solutions conform
 *      to the coding rules.
 *   2. Use the BDD checker to formally verify that your solutions produce 
 *      the correct answers.
 */


#endif

翻译成大白话,概括起来就是下面的意思:

前提

1.  机器使用补码,32位整数表示
2.  按算术方式执行右移
3.  当将整数移动超过字长时具有不可预测的行为

整数规则

1.不能使用for while if等语句
2.只能使用! ˜ & ˆ | + << >>运算符,不能使用任何其他操作,如&&、||、-或?
3.只能使用int
4.只能使用0-0xFF的常数(即0000 0000-1111 1111)
5.使用运算符数不超过限制(Max ops)
6.不能使用全局变量或调用函数等其他规则

7.不能使用数组、结构体

浮点数

1.允许使用循环和有条件的控制语句,如for while if
2.可以同时使用int,unsigned int,但不允许使用其他数据类型
3.使用运算符数不超过限制(Max ops)
4.不能使用数组,函数调用等其他规则
 

简单来说,就是需要你修改填充内置好的c语言函数声明,在上述一系列的条件约束限制下,让这个程序能跑起来,而且在后面的测评系统测评时可以获得尽可能高的Score 

二、bits.c函数详解

(1)bitAnd

题目要求:

使用按位或和按位取反实现按位与操作。

函数体:

解法:

使用我们离散数学中学过的德摩根定律,将 按位取反 和 或 操作,合并为与操作:

代码如下:

int bitAnd(int x, int y) {
  return ~(~x | ~y);
}

(2)getByte

题目要求:

将二进制串从低位起,字节编号依次为0-3,取出编号为n的字节。

函数体:

解法:

从低字节起,将二进制串编号为0-3,即分成四份。对于0x12345678,即分成12 34 56 78四份。要想取出编号为n的字节,首先要把需要取出的字节右移到最低位,即右移n个字节,也就是右移n*8个位,所以右移位数为n<<3,得到0x00123456

 

为了清除9-32位的数值,在这个案例中是把0x00123456中的001234清除,可以和0xFF做与运算,即ans & 0xFF

 

代码如下:

int getByte(int x, int n) {
//We move n three places to the right, so n is equal to n times 8. Then move the byte to be taken from x to the lowest position. Finally, we do the sum operation with 0xFF to clear the other bits
  int shiftLeftByte = x>>(n<<3);
  return shiftLeftByte & 0xFF;
}

(3)logicShift

题目要求:

实现逻辑右移(注意与算数右移区分)

函数体:

 

解法:

算数右移与逻辑右移的区别在于符号位的补充,只要在算数右移n位后,将前n位清除就可以实现逻辑右移。因此需要得到前n位为0,后32-n位为1的数与算数右移后的x相与。采用了将0x80000000移位先得到前n位为1,后32-n位为0的数后取反的方式得到这个数。

 

比如,在样例logicalShift(0x87654321,4)中,先将0x80000000向右移位3位,得到0xF0000000,然后取反,得到0x0FFFFFFF,将0x0FFFFFFF和(x>>4)作与运算操作即可

 

代码如下:

int logicalShift(int x, int n) {
  int tempNum = 1<<31;
  tempNum = ~((tempNum>>n)<<1);
  return tempNum & (x>>n);
}

(4)bitCount

题目要求:

计算二进制数串中1的个数

 函数体:

解法:

1.第一种方法,也是一眼能看出来的方法,就是暴力求解,也就是移位和0x1与得到1的个数

int bitCount(int x) {
    int ret = 0;
    for (int i = 0; i < 32; i++) {
        if (n & (1 << i)) {
            ret++;
        }
    }
    return ret;
}

显然,在这里是行不通的。因为题目规定不允许使用for循环和if语句。

正确的解法是使用分治的思想解决,下面我举一个例子,便于大家理解这种思想:

 

对于32位整数 (10111100011000110111111011111111)_{2},计算该二进制串中1的个数

划分问题:我们既然要查有多少个1,我们完全可以把这个32位数分成前后各16位数,即(1011110001100011 | 0111111011111111)_{2}

之后,计算每一部分中1的个数。
合并问题:把两部分中1的个数加起来,就是问题的最终解。
下面详细讲述每一步的具体步骤,已经明白原理的uu可以直接跳过哈

 

划分问题
我们再对其进行划分问题,即对于一个16位的数字,划分成前后各8位数字:

10111100 ∣ 01100011 ∣ 01111110 ∣ 11111111 

然后继续划分:

1011 ∣ 1100 ∣ 0110 ∣ 0011 ∣ 0111 ∣ 1110 ∣ 1111 ∣ 1111 

接着继续划分:

10 ∣ 11 ∣ 11 ∣ 00 ∣ 01 ∣ 10 ∣ 00 ∣ 11 ∣ 01 ∣ 11 ∣ 11 ∣ 10 ∣ 11 ∣ 11 ∣ 11 ∣ 11 

再来一次:

1 ∣ 0 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 0 ∣ 0 ∣ 0 ∣ 1 ∣ 1 ∣ 0 ∣ 0 ∣ 0 ∣ 1 ∣ 1 ∣ 0 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 0 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 ∣ 1 

这时候问题就变成了对于一个1位二进制数,求其中1的个数,显然,这非常简单:

1 → 1

0 → 0 

1就是1,0就是0

合并问题
对于相邻的两个位,我们进行相加合并:

1 + 1 = 10 , 1 + 0 = 01 , 0 + 1 = 01 , 0 + 0 = 00 

合并之后,我们的二进制串变成了这样:

01 ∣ 10 ∣ 10 ∣ 00 ∣ 01 ∣ 01 ∣ 00 ∣ 10 ∣ 01 ∣ 10 ∣ 10 ∣ 01 ∣ 10 ∣ 10 ∣ 10 ∣ 10 

也就是10 ∣ 11 ∣ 11 ∣ 00 ∣ 01 ∣ 10 ∣ 00 ∣ 11 ∣ 01 ∣ 11 ∣ 11 ∣ 10 ∣ 11 ∣ 11 ∣ 11 ∣ 11 的每一组中,前后两个数相加得到的一个2位二进制数代替原来的数字,那每组的意义也就显而易见了:

01 ∣ 10 ∣ 10 ∣ 00 ∣ 01 ∣ 01 ∣ 00 ∣ 10 ∣ 01 ∣ 10 ∣ 10 ∣ 01 ∣ 10 ∣ 10 ∣ 10 ∣ 10 

对于每一组数字的含义是:

第一组中1的个数的二进制表示|第二组中1的个数的二进制表示|第三组中1的个数的二进制表示|… …|第十六组中1的个数的二进制表示

当然,这步操作可以使用和0x55555555做与运算得到,即:

x = (0x55555555 & (x>>1))+(0x55555555 & x)//--伪代码

继续把相邻两组相加:

0011 ∣ 0010 ∣ 0010 ∣ 0010 ∣ 0011 ∣ 0011 ∣ 0100 ∣ 0100 

第一组中1的个数的二进制表示|第二组中1的个数的二进制表示|第三组中1的个数的二进制表示|… …|第八组中1的个数的二进制表示

这步操作使用和0x33333333做与运算得到,即

x = (0x33333333 & (x>>2))+(0x33333333 & x)//--伪代码

继续合并,相邻两组相加:

00000101 ∣ 00000100 ∣ 00000110 ∣ 00001000 

第一组中1的个数的二进制表示|第二组中1的个数的二进制表示|第三组中1的个数的二进制表示|第四组中1的个数的二进制表示

x = (0x0F0F0F0F & (x>>4))+(0x0F0F0F0F & x)//--伪代码

继续:

0000000000001001 ∣ 0000000000001110 

第一组中1的个数的二进制表示|第二组中1的个数的二进制表示

x = (0x00FF00FF & (x>>8))+(0x00FF00FF & x)//--伪代码

最后一次:

00000000000000000000000000010111 

x = (0x0000FFFF & (x>>16))+(0x0000FFFF & x)//--伪代码

到此,我们完成了把这个二进制数换成十进制,即23这一浩大的工作,完整代码如下所示:

int bitCount(int x) {
//Prepare the auxiliary binary string
  int auxiliaryNum1;
  int auxiliaryNum2;
  int auxiliaryNum3;
  int auxiliaryNum4;
  int auxiliaryNum5;
  auxiliaryNum1 = 0x55 + (0x55<<8);//0x5555
  auxiliaryNum1 = auxiliaryNum1 +(auxiliaryNum1<<16);//0x55555555
  auxiliaryNum2 = 0x33 + (0x33<<8);//0x3333
  auxiliaryNum2 = auxiliaryNum2 +(auxiliaryNum2<<16);//0x33333333
  auxiliaryNum3 = 0x0f + (0x0f<<8);//0x0F0F
  auxiliaryNum3 = auxiliaryNum3 +(auxiliaryNum3<<16);//0x0F0F0F0F
  auxiliaryNum4 = 0xff + (0xff<<16);//0x00FF00FF
  auxiliaryNum5 = 0xff +(0xff<<8);//0x0000FFFF
  
  x = (auxiliaryNum1 & (x>>1))+(auxiliaryNum1 & x);//Each group include 2 nums,which indicate the count of 1
  x = (auxiliaryNum2 & (x>>2))+(auxiliaryNum2 & x);//Each group include 4 nums
  x = (auxiliaryNum3 & (x>>4))+(auxiliaryNum3 & x);//Each group include 8 nums
  x = (auxiliaryNum4 & (x>>8))+(auxiliaryNum4 & x);//Each group include 16 nums
  x = (auxiliaryNum5 & (x>>16))+(auxiliaryNum5 & x);//Each group include 32 nums
  return x;
}

(5)bang

题目要求:

实现取非操作

函数体:

解法:

观察得知,当这个32位二进制串有任何一位为1时,输出为0;只有32位全为0时,才输出为1。所以,这道题本质上是判断二进制串是否含有1

 

与上一题类似,我们也可以使用分治的思想,具体操作如下:

 

高16位与低16位相或,低8位与8-16位相或,低4位与4-8位相或,低2位与2-4位相或,最低位和第二位相或,最终得到的第1位(最低位)的结果是32位相或的结果,也就是是否含1。但此时仍然不是最终结果,还需要把2-32位给清除为0(这时候2-32位数值是杂乱无章,也许是0也许是1的),并保持第1位数值不变,可以使用如下操作:

ans = ans & 0x1

操作结束后,如果二进制串含有1,则变成0x0000 0001;反之,如果二进制串不含有1(即全0),则变成0x0000 0000。

 

注意,输出时要求取反,即最低位是1则输出0,最低位为0则输出1,这时候可以使用异或操作,将ans与0x1异或

ans = ans ^ 0x1

正好可以把最低位取反,且保证2-32位仍然为0,保持不变。最后输出就可以了。

代码如下:

int bang(int x) {
  int ans = (x | x>>16);
  ans = ans | ans>>8;
  ans = ans | ans>>4;
  ans = ans | ans>>2;
  ans = ans | ans>>1;
  return (ans & 0x1) ^ 0x1;
}

(6)tMin

题目要求:

求最小的有符号数

函数体: 

解法:

在32位系统中,最小的int类型有符号数为0x8000 0000,即0b1000 0000 0000 0000 0000 0000 0000 0000,十进制数值为-2^31。直接将1右移31位即可得到:

int tmin(void) {
  return 1<<31;
}

(7)fitsBits

题目要求:

求x是否可用n位补码表示,若可以则返回1,反之则返回0。

函数体:

解法:

 在1-32位的二进制串中,如果一个数可以用n位补码表示,那么就说明这个二进制数的第n+1到32位都是没有意义的,都是属于第n位的扩展。

 

对于正数,体现为第n+1到32位都是0;对于负数,体现为第n+1到32位都是1。

 

那么,将这个二进制数左移32-n位,再右移32-n位,得到的数必然是和原数相等的。其中,“相等”可以用异或 +逻辑取反 判断。

 

代码如下:

int fitsBits(int x, int n) {
  int temp;
  temp = 32 + (~n + 1);//32 - n
  return !(x ^ ((x<<temp)>>temp));
}

注意:如果./btest后出现以下报错:

[code=csharp]
Test fitsBits(-2147483648[0x80000000],1[0x1]) failed... ...Gives 1[0x1]. Should be 0[0x0]

可以vim进入Makefile文件后,关掉优化,把

CFLAGS = -O -Wall -m32

 改为

CFLAGS = -Wall -m32

原因:-O表示编译优化选项,其后可跟优化等级0\1\2\3,默认是0,不优化

主要区别在于第一个设置中包含了 -O 优化标志,而第二个设置没有。因此,当使用第一个 CFLAGS 设置编译代码时,编译器会尝试优化生成的代码以提高其性能或减小其大小;而当使用第二个设置时,编译器不会进行优化,只是输出警告信息并生成 32 位代码。

(8)divPwr2

题目要求:

计算x除2的n次方,其中正数向下取整,负数向上取整。

函数体:

解法: 

首先,计算x除2的n次方,想当然就是x>>n,但是考虑到如果x是个负数,那么就需要修改为(x+1)>>n,所以总体来说,需要定义一个偏移量bias,取值为1或者0,来对正负数做一个区分。

return (x + bias)>>n;

对于bias:

①若x为正数或者0,则bias=0

②若x为负数,则bias=(2^n)-1

③若n等于0,则bias=0

 

综上所述,

bias = ((1<<n) + (~1 + 1)) & (x>>31);

与《深入理解计算机系统》课本上面公式一致。 

代码如下: 

int divpwr2(int x, int n) {
    int bias = ((1<<n) + (~1 + 1)) & (x>>31);
    return (x + bias)>>n;
}

(9)negate

题目要求:

求-x,即将x取反再+1

函数体:

解法:

 根据二进制中负数的定义,即为正数的二进制串取反再加一;负数的负数亦然。

int negate(int x) {
  return ~x + 0x1;
}

(10)isPositive

题目要求:

若数字大于0则返回1,否则返回0(同布尔值)

函数体: 

解法: 

对于正数和负数来说,二进制串的第32位决定了数字的正负,x>>31为0则为正数,若x>>31为1则为负数。

 

但是注意,要特殊处理0x0的情况,也就是说0要和负数统一起来,在同一个集合内,可以使用逻辑取反操作“!”(注意和按位取反操作~区分),将x做两次取反处理,即!!x。最后,将x>>31的取反值,和!!x做与运算,得到最终结果。

 

代码如下:

int isPositive(int x) {
  return (!(x>>31)) & (!!x);
}

(11)isLessOrEqual

题目要求:

两数作差,判断是否大于0。若大于0则返回0,若小于0则返回1。

函数体:

解法: 

首先取出x和y的符号位,即numX和numY,通过(x>>31)&1操作取得。

int numX;
int numY;
numX = (x>>31) & 0x1;//0x00000001 or 0x00000000
numY = (y>>31) & 0x1;//0x00000001 or 0x00000000

然后判断符号位是否相同。如果相同,则 (!(numY ^ numX))为真;反之numX & (!numY)为真。

 

1.如果符号位不同,直接判断符号位,得到结果。返回numX & (!numY) 

①若x>=0,y<0,则 !numY为0x1,numX为0x00000000,numX & (!numY) 为0

②若x<0,y>=0,则!numY为0x00000000,numX为0x1,numX & (!numY) 为1

2.如果符号位相同,先用y减去x,即y + (~x + 0x1),再取符号位,若y>x则符号位为0,反之则为1

 

代码如下:

int isLessOrEqual(int x, int y) {
  int numX;
  int numY;
  numX = (x>>31) & 0x1;//0x00000001 or 0x00000000
  numY = (y>>31) & 0x1;//0x00000001 or 0x00000000
  return (numX & (!numY)) | ((!((y + (~x + 0x1))>>31)) & (!(numY ^ numX)));
}

(12)ilog2

题目要求:

求以2为底的对数,且向下取整。不考虑负数和0。

函数体:

解法:

求以2为底的对数,就相当于找到最高位1的位置,然后将最高位1对应的位数,减去1,就得到其对应的权值。

 

方法一:

将最高位1右侧的位全部通过移位覆盖,再利用bitCount函数进行统计,得到的结果再减去1,就得到了结果。

 

例如,对于十六进制数0x0010A509,通过移位覆盖,得到0x001FFFFF,再统计得到1的个数为21,减去1得到20,即为最终结果。

具体的移位过程,可以通过先右移1位,再右移2位,再右移4位...最后右移16位(依次×2)得到

x = x | (x>>1);
x = x | (x>>2);
x = x | (x>>4);
x = x | (x>>8);
x = x | (x>>16);//All coverd

 方法一代码如下:

int ilog2(int x) {
  int auxiliaryNum1;
  int auxiliaryNum2;
  int auxiliaryNum3;
  int auxiliaryNum4;
  int auxiliaryNum5;
  auxiliaryNum1 = 0x55 + (0x55<<8);//0x5555
  auxiliaryNum1 = auxiliaryNum1 +(auxiliaryNum1<<16);//0x55555555
  auxiliaryNum2 = 0x33 + (0x33<<8);//0x3333
  auxiliaryNum2 = auxiliaryNum2 +(auxiliaryNum2<<16);//0x33333333
  auxiliaryNum3 = 0x0f + (0x0f<<8);//0x0F0F
  auxiliaryNum3 = auxiliaryNum3 +(auxiliaryNum3<<16);//0x0F0F0F0F
  auxiliaryNum4 = 0xff + (0xff<<16);//0x00FF00FF
  auxiliaryNum5 = 0xff +(0xff<<8);//0x0000FFFF
  
  x = x | (x>>1);
  x = x | (x>>2);
  x = x | (x>>4);
  x = x | (x>>8);
  x = x | (x>>16);//All coverd
  
  x = (auxiliaryNum1 & (x>>1))+(auxiliaryNum1 & x);//Each group include 2 nums,which indicate the count of 1
  x = (auxiliaryNum2 & (x>>2))+(auxiliaryNum2 & x);//Each group include 4 nums
  x = (auxiliaryNum3 & (x>>4))+(auxiliaryNum3 & x);//Each group include 8 nums
  x = (auxiliaryNum4 & (x>>8))+(auxiliaryNum4 & x);//Each group include 16 nums
  x = (auxiliaryNum5 & (x>>16))+(auxiliaryNum5 & x);//Each group include 32 nums
  return x + (~0x1 + 0x1);//x - 1
}

方法二:

 基于二分的思想,寻找最高位1的位置。

 

大致思想如下:先将检测前16位和后16位,如果后16位存在1,则res加上16,继续检测17-24位和25-32位,若没有则检测1-8位和9-16位。以此类推。

 

例如,对于二进制数0b00000110 11001100 11001100 11001100,

①首先将前16位和后16位分隔,指针指向正中间,0b00000110 11001100 | 11001100 11001100

②然后x右移16位,检测到17-32位有1,则继续检测17-24位和25-32位,指针向左移8位,0b00000110 | 11001100 11001100 11001100

③然后x再右移8位,检测到25-32位有1,则继续检测25-28和29-32位,指针向左移4位,0b0000 | 0110 11001100 11001100 11001100

④接着,检测到29-32位没有1,则检测25-28位,指针向右移2位,0b000001 | 10 11001100 11001100 11001100

⑥检测到27-28位有1,指针向右移动1位,0b00000 | 110 11001100 11001100 11001100

⑦最后,确定为第27位为最高位1,返回27-1=26

指针左移指针右移
第一次+16+0
第二次+8+0
第三次+4+0
第四次+2+0
第五次+1+0

方法二代码如下:

int ilog2(int x) {
  int res;
  res = 0;
  res += (!!(x>>16))<<4;
  res += (!!(x>>(res + 8)))<<3;
  res += (!!(x>>(res + 4)))<<2;
  res += (!!(x>>(res + 2)))<<1;
  res += (!!(x>>(res + 1)));
  return res;
}

(13)float_neg

题目要求:

浮点数取反。

函数体:

 

解法:

将符号位(第32位)取反即可。

NaN通过尾数和阶码使用if判断,当尾数不为0,阶码都为1时,直接返回原值uf

尾数通过uf<<9实现,阶码通过uf>>23实现。

代码如下: 

unsigned float_neg(unsigned uf) {
  if((uf<<9) && (((uf>>23) & 0xff) == 0xff))
  return uf;
  else return uf ^ 0x80000000;
}

(14)float_i2f

题目要求:

将整型转换为浮点数表示。

函数体:

解法:

这题细节处理比较多。根据IEEE标准,先进行规格化处理。

 

符号位可以直接使用原int型的符号位,即第32位。

阶码部分,为了取得原值的2的最高次方,希望进行与ilog2类似的操作,因此先将负数取反+1变为整数,同时注意到0x80000000和0两个特殊值,直接判断排除这两种情况。求对数时由于可以使用while语句,采用移位的方式实现,设E=30,移位开始判断第31位是否为1,E—直到找到最高位的1。最终的到的E就是所求对数,加上偏移量127并移位到对应的位置,得到阶码部分。

尾数部分,首先要得到完整的尾数,完整的尾数是最高位1右侧的数加上补充的0。E+1是最高位1的位置,左移32-E-1+1=32-E就可以将最高位1移出,剩下完整的尾数。浮点数中尾数只有23位,因此需要右移9位,并清除补充的位。

最后,还要对舍入进行处理。主要是对被舍弃的9位和尾数最低位做判断,按照向偶舍入的规则进行判断即可。

 

代码如下:

unsigned float_i2f(int x) {
  if(x == 0)
  return 0;
  else if(x ==0x80000000)
  return 0xcf000000;
  int s;
  int exp;
  int frac;
  int E = 30;
  int Bias = 127;
  int res = 0;
  unsigned int roundOff;
  s = x & 0x80000000;
  if(s != 0)
  x = ~x + 1;
  while(!((x>>E) & 0x1)) E--;
  exp = (E + Bias)<<23;
  x = (x<<(32 - E));
  frac = (x>>9) & 0x007FFFFF;
  res = s | exp | frac;
  roundOff = x & 0x000001FF;
  if(roundOff & 0x00000100)
  {
    if((roundOff & 0xff) || (res & 0x1))
    {
      res = res + 1;
    }
  }
  return res;
}

(15)float_twice

题目要求:

返回2 * uf。

函数体:

解法:

①对于规格化数阶码,改变E的值,使E=E+1即可,可以将原二进制串加上0x800000得到。

②对于非规格化数,由于可以和规格化数平滑衔接,只需要左移1位,补充符号位s即可。

③对于特殊值,直接返回原值。

 

代码如下:

unsigned float_twice(unsigned uf) {
  int s;
  int exp;
  s = uf & 0x80000000;//Fetch symbol bit
  exp = (uf<<1)>>24;
  if(exp != 0 && exp !=0xff)//Normalized number
  uf += 0x800000;
  else if(exp == 0)//Unnormalized number
  uf = ((uf<<0x1) | s);//NaN
  else{}
  return uf;
}

三、运行

代码完善后,进入终端,在datalab-handout目录下依次输入下面命令:

./dlc bits.c

无报错证明程序内部的编码符合实验要求的编码规范

./dlc -e bits.c

无报错证明补充每一个函数时使用的运算符数量符合实验要求的规范

make clean

清除旧版本的btest运行程序,成功清除

make all

成功生成新版本btest运行程序

注意:如果编译过程中出现fatal error: bits/libc-header-start.h: No such file or directory

原因是环境没有完善造成的,通过执行以下命令来完善编译环境,可以输入:

sudo apt-get install gcc-multilib

安装gcc-multilib库,然后就可以成功编译了

./btest

15个Function中无Errors报错,拿到了满分,证明了补充的函数的正确性。

四、实验报告 

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一二爱上蜜桃猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值