一 前言

  看了csapp2e第二章,感觉讲的很透彻,理解了一些以前学组成原理没有学懂的东西。这章最让我感觉深刻的还是计算机是怎么实现c语言中的基本数据类型的表示和操作的,这对程序员理解程序无疑是帮助巨大的。也正如这本书的题目--以编程人的视角来理解计算机系统(自己这么翻译的,呵呵!)。第二章的homework很多,很多题目都是和位打交道,并且有些题目还只能使用位操作符,不允许使用循环,比较运算符,这让平时很少使用位操作的我来说确实很捉急啊!不过这也正是锻炼自己位操作的大好机会嘛!下面选了几道我认为还是挺不错的题目拿来分享下,由于homework是没有答案的,所以下面解答只是个人的见解。

二 homework

 2.65

  这道题目要求写下面这样一个函数:

  int even_one(unsigned x);

  当x展开的比特位中包含偶数个1时返回1,否则返回0。假设x的位数是32位。只能使用位操作,逻辑操作,+、-操作,并且使用这些操作的总次数不能超过12次。

  最直观的的方法当然是循环32次统计x位中1的个数,不过题目要求不能使用循环,所以显然是不可以的。既然不能使用循环,想想什么位操作能统计1的个数呢?很显然是没有的,不过题目也没让统计1的个数,换个角度,它只要统计1的个数为偶数还是奇数个就行了。很直观会想到用异或操作,具体如下:

int even_ones(unsigned x)
{
x ^= (x >> );
x ^= (x >> );
x ^= (x >> );
x ^= (x >> );
x ^= (x >> );
return !(x & );
}

2.66  

  这道题目要求写下面这样一个函数:

  int leftmost_one(unsigned x);

  实现x展开的比特位中只保留最高位1,并将结果作为函数值返回,如果x为0,返回0。例如x=0xff00,函数返回0x8000。函数中使用的操作要求和题目2.65一样。

  这道题目其实是确定最高位的1在x中是第几位,不过不能使用循环来确定。那就只有把最高位的1右移来填充比这个最高位低的所有位,这样其实就已经确定了最高位1的位置了,具体如下:

int leftmost_one(unsigned x)
{
x |= x >> ;
x |= x >> ;
x |= x >> ;
x |= x >> ;
x |= x >> ;
x &= ~(x >> );
return x;
}

2.73    

  这道题目要求写下面这样一个函数:

  int saturating_add(int x, int y);

  如果x + y向上溢出,则函数返回TMax,如果x + y向下溢出,则函数返回TMin,其它都返回x + y。函数中使用的操作要求和题目2.65一样。

  先判断x + y是否会溢出,如果会溢出,则根据x的符号位判断是上溢出还是下溢出就可以了。

int saturating_add(int x, int y)
{
int sum = x + y;
int w = (sizeof(int) << ) - ;
int xs = x >> w;
int ys = y >> w;
int sums = sum >> w;
(xs == ys) && (xs != sums) && (!xs && sum = TMax || xs && sum = TMin);
return sum;
}

2.81

  对于给定的随机数x和y,它们的类型都为int型,判断c表达式(x > y) == (-x < -y)是否总是产生1或0?

  直观上这个等式应该总是1,但由于c中的int类型在计算机中使用补码表示的。而对于补码,TMin是个很特别的数,它的反依旧是TMin,即-TMin == TMin。所以当x = TMin,y = 0时,上面的等式为0。

2.88

  题目先定义了六个变量,并假设这些变量都是在IA32的机器上定义的,分别如下:

  int x = random();

  int y = random();

  int z = random();

  double dx = (double)x;

  double dy = (double)y;

  double dz = (double)z;

  判断下面的c表达式是否总是为1或0:

  A. (double)(float)x == dx

  B. dx + dy == (double)(x + y)

  C. dx + dy + dz == dz + dy + dx

  D. dx / dx == dy / dy

  A并非总是1,由于单精度的小数位数只有23位,加隐藏的1位,最高也就24位,所以很显然对于32位的int,有些数它是无法精确表示的,如2 ^ 24 + 1,它是单精度最小的无法精确表示的int数。

  B也是并非总是1,由于x + y可能会溢出。

  C总是为1的,由于dx,dy,dz是双精度,小数位数有52位,对于只有32的int行来说足够了。当如果dx, dy, dz是float(不考虑dx,dy,dz是由int型转化过来的),就不是总为1了,正如A中所讲的,单精度的小数部位只有24位,如果dx相对dy很小的话,dx会被舍弃掉。如dx = 3.14, dy = 1e10, dz = -1e10, dx + dy + dz == 0,而dz + dy + dx = 3.14。

  D并非总是1,如果dx或者dy为0的话,会出现除0情况,这是不允许的。

2.93

  这道题目要求写下面这样一个函数:

  float_bits float_half(float_bits f);

  其中float_bits是32位的无符号整数类型。参数f是其浮点数对应的比特位,函数要求计算0.5 * f,并返回,如果f是NaN(不是一个浮点数),则返回f。并且在电脑上穷举f的2^32种情况来比较电脑中的浮点操作结果和你自己计算的浮点操作的结果。如果需要舍入的话,用round-to-even。

  这道题目要求自己实现浮点数的操作,这其实是很不习惯的(反正对于我来是这样,毕竟都没这么搞过啊!)。这道题目需要特殊处理的是浮点数的指数部分(加了偏移量的)为0或1的情况,而其它情况只要对指数减1。如果为0,则必须要将小数向右移动1位,不过这就涉及到舍入的问题了;如果为1,那么需要在小数点中加上隐藏位1,再向右移动一位,并且指数减1,这样就转化为了指数为0的情况。具体实现如下:

#include <stdio.h>

typedef unsigned float_bits;

float_bits float_half(float_bits f)
{
unsigned sign = f >> ;
unsigned exp = f >> & 0xff;
unsigned frac = f & 0x7fffff; if (exp == 0xff)
return f;
if (exp > )
{
exp--;
if (exp == )
frac += << ;
}
if (exp == )
{
if ((frac & 0x3) == 0x3)
frac = (frac >> ) + ;
else
frac >>= ;
} return ((sign << ) | (exp << ) | frac);
}
int main(void)
{
unsigned i, j;
float f1, f2; for (i = 0x3f800000U; i <= 0xffffffffU; i++)
{
f1 = *((float *)&i);
f1 *= (float)0.5;
j = float_half(i);
f2 = *((float *)&j);
printf("%#x : %f %f\n", i, f1, f2);
}
return ;
}

2.94

  这道题目要求写下面这样一个函数:

  float_bits float_twice(float_bits f);

  题目的要求和2.93一样,只是要求2 * f。

  需要特殊处理的是指数部分(加了偏移量的)为0的情况。当指数为0时,左移小数部分1位,如果小数部分的最高位1为第23位(从0开始数),则指数加1,并且小数部分去掉这个最高位的1,否则只要左移小数部分1位就可以了。其它情况只需要将指数加1。具体实现如下:

#include <stdio.h>

typedef unsigned float_bits;

float_bits float_twice(float_bits f)
{
unsigned sign = f >> ;
unsigned exp = (f >> ) & 0xff;
unsigned frac = f & 0x7fffff; if (exp == 0xff)
return f;
if (exp == )
{
frac <<= ;
if (frac & 0x800000)
{
frac &= 0x7fffff;
exp++;
}
}
else
exp++;
return ((sign << ) | (exp << ) | frac);
}
int main(void)
{
unsigned i, j;
float f1, f2; for (i = 0x37800000U; i <= 0xffffffffU; i++)
{
f1 = *((float *)&i);
f1 *= (float)2.0;
j = float_twice(i);
f2 = *((float *)&j);
printf("%#x : %f %f\n", i, f1, f2);
}
return ;
}

2.95

  这道题目要求写下面这样一个函数:

  float_bits float_i2f(int i);

  函数实现对int类型到float类型的转化。在c中我们可能用简简单单的强制类型转化就搞定了,不过如果是自己来实现,还是有一定的难度的。

  这道题主要注意三个方面。一是i为特殊数字的时候,如i=0, i=TMin;二是i为负数的时候,需要求出i的原码来,由于int类型在机器中都是用补码表示的;三是慎重考虑舍入的情况。具体实现如下:

#include <stdio.h>

typedef unsigned float_bits;
#define TMin 0x80000000 float_bits float_i2f(int i)
{
unsigned sign, exp, frac, last_bit; sign = i >> ;
if (i == TMin) //处理i为最小负数的时候
{
exp = ;
frac = ;
}
else if (i == ) //处理i为0
{
exp = ;
frac = ;
}
else
{
if (sign) //如果i为负数,需要求其原码
frac = (~i + ) << ;
else
frac = i << ;
exp = ;
while (!(frac & 0x80000000))
{
exp++;
frac <<= ;
}
exp = - exp;
last_bit = ((frac >> ) & );
if (!last_bit) //处理舍入,对于最后1位为0
{
if ((frac & 0xff) == 0x80 || !((frac & 0xff) & 0x80)) //舍弃的位离0更近
frac = ((frac << ) >> );
else
frac = ((frac << ) >> ) + ;
}
else //处理舍入,对于最后1位为1
{
if ((frac & 0xff) == 0x80 || ((frac & 0xff) & 0x80)) //舍弃的位离1更近
{
frac = ((frac << ) >> ) + ;
if (frac & 0x800000)
{
exp++;
frac = ;
}
}
else
frac = ((frac << ) >> );
}
}
return ((sign << ) | (exp << ) | frac);
}
int main(void)
{
unsigned i;
int j, k;
float f1, f2; for (i = 0x80000000U; i <= 0xffffffffU; i++)
{
j = (int)i;
f1 = (float)j;
k = float_i2f(j);
f2 = *((float *)&k);
printf("%#x : %f %f\n", i, f1, f2);
}
return ;
}

 2.96

  这道题目要求写下面这样一个函数:

  int float_f2i(float_bits f);

  函数实现对float类型到int类型的转化。如果转化中溢出或者f是NaN,则返回0x80000000。

  这道题和2.95差不多,主要是考虑溢出的情况。

#include <stdio.h>

typedef unsigned float_bits;
#define TMin 0x80000000 int float_f2i(float_bits f)
{
unsigned sign, exp, frac, last_bit;
int i; sign = f >> ;
exp = (f >> ) & 0xff;
frac = f & 0x7fffff; if (exp == && frac == && sign == ) //处理f = TMin的情况
i = TMin;
else if (exp > || exp == 0xff) //处理f上溢出或f是NaN的情况
i = TMin;
else if (exp < ) //处理f下溢出
i = ;
else
{
exp -= ;
frac |= 0x800000;
if (exp > )
{
exp -= ;
frac <<= exp;
}
else if (exp < )
{
exp = - exp;
frac >>= exp;
}
if (sign == )
i = (~frac) + ;
else
i = frac;
}
return i;
}
int main(void)
{
unsigned i;
int j, k;
float f; for (i = 0x3fbfff70U; i <= 0xffffffffU; i++)
{
f = *(float *)&i;
j = (int)f;
k = float_f2i(i);
printf("%f : %d %d\n", f, j, k);
}
return ;
}

csapp2e-chapter2-homework的更多相关文章

  1. python核心编程笔记——Chapter2

    对于.py文件,任何一个空的式子都不会有什么输出,如下: #!/usr/bin/env python #-*-coding=utf-8-*- #无任何效果,空语句 1 + 2 * 4 对于i++,++ ...

  2. bzoj 4320: ShangHai2006 Homework

    4320: ShangHai2006 Homework Time Limit: 10 Sec Memory Limit: 128 MB Description 1:在人物集合 S 中加入一个新的程序员 ...

  3. HDU 1789 Doing Homework again(贪心)

    Doing Homework again 这只是一道简单的贪心,但想不到的话,真的好难,我就想不到,最后还是看的题解 [题目链接]Doing Homework again [题目类型]贪心 & ...

  4. hdu-1789-Doing Homework again

    /* Doing Homework again Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...

  5. 《深入PHP与jQuery开发》读书笔记——Chapter2

    Pro PHP and jQuery Chapter2 总结 1.理解jQuery脚本的基本行为 jQuery事实上沿用了JavaScript的那一套东西,几乎所有方法都支持链式调用,也就是说方法可以 ...

  6. HDU 1789 Doing Homework again (贪心)

    Doing Homework again http://acm.hdu.edu.cn/showproblem.php?pid=1789 Problem Description Ignatius has ...

  7. Doing Homework 状态压缩DP

    Doing Homework 题目抽象:给出n个task的name,deadline,need.  每个任务的罚时penalty=finish-deadline;   task不可以同时做.问按怎样的 ...

  8. 机器学习 —— 概率图模型(Homework: Exact Inference)

    在前三周的作业中,我构造了概率图模型并调用第三方的求解器对器进行了求解,最终获得了每个随机变量的分布(有向图),最大后验分布(双向图).本周作业的主要内容就是自行编写概率图模型的求解器.实际上,从根本 ...

  9. ###《Effective STL》--Chapter2

    点击查看Evernote原文. #@author: gr #@date: 2014-09-15 #@email: forgerui@gmail.com Chapter2 vector和string T ...

  10. Learning WCF Chapter2 Data Contracts

    A data contract describes how CLR types map to XSD schema definitions. Data contracts are the prefer ...

随机推荐

  1. SICAU教务系统登录密码加密算法的VB方式实现

    关于一个算法.这个算法是SICAU教务系统在账号登录时采取的一个加密算法.算法的实现并不复杂. 具体如下: Function Form1pwdvalue(ByVal pwdvalue As Strin ...

  2. 关于C#调用非托管动态库方式的性能疑问

    最近的项目中,因为一些原因,需要C#调用非托管(这里为C++)的动态库.网上喜闻乐见的方式是采用静态(DllImport)方式进行调用.偶然在园子里看到可以用动态(LoadLibrary,GetPro ...

  3. java执行linux命令

    package com.gtstar.collector; import java.io.BufferedReader;import java.io.IOException;import java.i ...

  4. Hibernate多对多双向关联的配置

    Hibernate的双向多对多关联有两种配置方法:那我们就来看看两种方案是如何配置的.  一.创建以各自类为类型的集合来关联 1.首先我们要在两个实体类(雇员<Emploee>.工程< ...

  5. JSP :运行最简单的 JSP 程序

    160916 1. 代码和显示效果 <%@ page contentType="text/html; charset=GB2312" %> <%@ page im ...

  6. Myeclipse中web project各种常见错误及解决方法(持续更新)

    创建web project时的问题 error:Install Dynamic web Module Facet卡住 solution:把网络关掉再创建就可以 Servlet error:The se ...

  7. C#_技巧:字符串分组排序

    思想//GroupBy+ToDictionary实现Dictionary<> List<string> list = new List<string>(); //l ...

  8. TaintDroid深入剖析之启动篇

    ​1 背景知识 1.1   Android平台软件动态分析现状 众所周知,在计算机领域中所有的软件分析方法都可以归为静态分析和动态分析两大类,在Android平台也不例外.而随着软件加固.混淆技术的不 ...

  9. ABP理论学习之审计日志

    返回总目录 本篇目录 介绍 配置 通过特性开启/关闭 注意 我项目中的例子 介绍 维基百科说: "审计跟踪(也叫审计日志)是与安全相关的按照时间顺序的记录,记录集或者记录源,它们提供了活动序 ...

  10. [译] 给PHP开发者的PHP源码-第一部分-源码结构

    文章来自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-ch 原文:http://blog.ircmaxel ...