深入理解计算机系统(csapp)阅读笔记——第二章信息的表示和处理

1.信息存储

  • 机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数字来标识,称为它的地址,所有可能地址的集合就成为虚拟地址空间
  • 每台计算机都有一个字长,虚拟地址就是以这样的一个字来编码的。字长决定了虚拟地址空间的最大大小
  • long在32位程序下是4个字节
    在这里插入图片描述
  • 大端和小端:
    在这里插入图片描述
    大多Inter兼容机都只用小端模式。另一方面,IBM和Oracle的大多数机器则是按打断工作。
  • C语言中字符串被编码为一个null(其值为0)字符结尾的字符数组。
  • 位向量的运用——有限集合
    在这里插入图片描述
  • 不使用第三个临时变量对两个数进行交换
//***该方法的缺陷,如果传入的x,y所在地址相同,即本身和本身交换,会让本身置0
void inplace_swap(int *x,int *y)
{
	//x和y按位亦或得到不相交的部分,即x+y
	*y = *x ^ *y;
	//原来的x和上步骤得到的x+y按位亦或得到不相交的部分,即y,赋给x
	*x = *x ^ *y;
	//上步骤得到的x(原来的y)与x+y按位亦或得到不相交的部分,即x,赋给y
	*y = *x ^ *y;
}

位级运算的一个常见用法就是实现掩码运算,&可以用来将某些位置0,|可以用来将某些位置1。异或的用法:如果想让一个数的某些位不变,可以异或0,某些位求补则异或1。
**位运算掩码例题:
在这里插入图片描述

int x = 0x87654321;
printf("%x", x & 0xFF);
printf("%x", x ^~0xFF);
printf("%x", x | 0xFF);
  • 可以使用集合的概念来理解该式
    在这里插入图片描述
  • 逻辑右移是填补0,算术右移是填补最高有效位的值。

2.整数表示

  • 整数的数据与算数操作术语
    在这里插入图片描述- 计算机中的所有数都是以补码形式存储的。
    在这里插入图片描述
    特别要注意的是TMin
  • 无符号数和符号数之间的强制转换不改变数字,只改变解释数据的方式
  • 扩展数字:
    要将一个无符号数转换为一个更大的数据类型,我们只要地在表示的开头添加0,这种运算被称为零扩展。而补码数的符号扩展则是扩展最高有效位。
  • 截断数字:直接从左到右截断
  • 无符号数到有符号数的隐式强制转换导致的错误,观看下面的程序:
float sum(float a[],unsigned length)
{
	int i;
	float result = 0;
	for(i=0;i<=length-1;i++)
		result += a[i];
	return result;
}

程序乍看没什么问题,但是当length=0的时候就会出现问题,因为length为无符号数,length-1也会被计算机认为是无符号数,从而程序进入了循环,并访问了数组的非法区域。
无符号数的运算特别注意,不管怎么样算出来不可能是负值

3.整数运算

(1)无符号加法

  • 如何检测无符号数加法中的溢出:
    令s=x+y,当且仅当s<x或s<y时,发生了溢出。
  • 设计检测两个无符号数相加是否溢出的函数
//如果不会溢出就返回1
int uadd(unsigned x,unsigned y)
{
	return x+y<x?0:1;
}

(2)补码加法

  • 只有同正同负才会溢出,判断溢出的方法是看最高有效位与原来的两个数的最高有效位是否相同,不相同则溢出。
  • 设计检测两个补码加法是否溢出
    //如果不会溢出就返回1
int add(int x,int y)
{
	if(x*y>0)
	{
		int sum = x+y;
		return sum*x>0?1:0;
	}
	return 1;
}
  • 判断下面的程序怎么出错了
int add(int x,int y)
{
	int sum = x+y;
	return (sum-x==y)&&(sum-y==x);
}

补码溢出和无符号溢出是不一样的,补码溢出就算溢出了也没有截断,故上述程序永远返回没有溢出。

  • 补码的非:对于一个特殊的TMin的来说,他的非是自己本身
  • 以下程序是判断x-y是否溢出,溢出就返回1,判断什么时候会出问题
int sub(int x,int y)
{
	return add(x,-y);
}

基本上是正确的,唯一要注意的是当y=TMin的时候,-y也等于TMin,一个作为有符号数-128,一个作为无符号数128。这时候该程序就会出错。
所以TMin一般参与程序的测试。

(3)无符号乘法

在这里插入图片描述

(4)补码乘法

  • 乘以常数的时候编译器的优化:
    (1)乘以2的k次方,等于将一个数左移k位。
    (2)任意常数:
    在这里插入图片描述
    两种A和B编译器会选择哪一种:
    当n=m时选形式A,当n=m+1任意,n>m+1选B
    两种特殊情况:
    第一种负数,直接求反就行
    第二种是B的变种公式,即如果连续的1中有0,则把对应的项给减掉。
K 表达式
-6 (x<<1)-(x<<3)
55 (x<<6)-(x<<3)-x

(4)除法

  • 乘以常数的时候编译器的优化:
    • 得到向下取整的整数:
      除以2的k次方,等于将一个数(无符号逻辑,补码算术)右移k位
    • 得到向上取整的整数:
      ( x+(1<<k)-1)>>k(x为被除数,k为位数)

4.浮点数

  • 定点表示法:
    在这里插入图片描述
  • IEEE浮点表示:
    在这里插入图片描述
  • float和double两种格式:
  • 单精度格式的分类:
    在这里插入图片描述
    • 规格化的值
      在这里插入图片描述
      • exp字段
        考虑科学计数法表示整数的时候,E取值为正数或0;表示小数的时候,E取值为负数,因此E应为一个有符号整数。这里没有采取再设置一个E的符号位的方式,而是采用偏置形式来表示有符号整数。
        简单说来就是设定一个数,我们称之Bias。若E的原始值大于Bias(在Bias的右边),则阶码的值为正数;若E的原始值小于Bias(在Bias的左边),则阶码的值为负数。很明显这里将exp字段设计的更加复杂也是为了妥协。
        E=e-Bias
        e是无符号数
        Bias=2^(k-1)-1
        以exp字段为1001 0011为例,Bias为0111 1111=127
        E=1001 0011 - 0111 1111=0001 0011=19

      • frac字段
        frac字段为小数字段,鉴于一个二进制小数可以化为1.x*2^E的形式,可以将1.x中的1省略不表示,反正每一个数最终都包含1,这就叫做共识。因为省略了1,因此在最终计算结果的时候需要加1,尾数定义为M=1+f,f的二进制表示为0.fn-1 … f1 f0

    • 非规格化值
      在这里插入图片描述
      非规格化的值exp字段全为0,阶码值E=1-Bias,尾数M=f。
      非规格化数两个用途:
      (1)表示数值0
      (2)表示非常接近于0.0的数
      在规格化值的规则设定中,我们默认M是一个范围为(1<=M<2)的数,这就导致我们可以表示0.00…01这个很接近0的小数,却没法用浮点数表示0。考虑到这点,我们规定当exp字段全为0的时候,阶码值E=1-Bias(127),而根据公式M*2^E可以看出,无论M在表示范围内取何值,都避免不了结果趋近于0.0的命运,此即权值对数的影响。
      回到主线,按照规格化的方式(M=1+f),虽然我们也最终可以无限接近于0.0,但是总有个1在那里,因此我们设定了非规格化的规则,M=f。这样一来,我们最终将的到0(M=0)。
      需要注意的是,根据s字段的值,我们将会有+0.0(s=1)和-0.0(s=0)。这两个值在某些方面被认为是不同的,至少在存储中不同。
    • 特殊值
      在这里插入图片描述 exp字段全为1。
      我们还是先分析权,E将会是一个很大的值,导致权很大,可以用来表示无穷。
      若frac字段全为0,得到的值表示无穷,当s=0为+无穷,s=1表示-无穷。
      若frac字段不全为0,结果值被称为NaN,即不是一个值(Not a Number),可以用来表示某些运算导致出现非实数的结果,例如对-1求平方根,或计算正无穷-正无穷。
  • 示例:
    在这里插入图片描述
  • 整数转换成浮点数:

在这里插入图片描述
小数点左边往左补0,右边往右补0

  • 题目:
    对于一种具有n位小数的浮点格式,给出不能准确描述的最小正整数的公式(因为想要准确表示它可能需要n+1位小数)。假设阶码字段长度k足够大,可以表示的阶码范围不会限制这个问题。
  • 思路:
    整数的二进制表示的低位,和浮点表示的小数部分的高位是匹配的,可以通过移位来得到浮点表示的小数部分。
    因为要求的是正整数的表示,因此可以排除非规格化的数值范围,因为这些值全部都小于1。在考虑规格化的数值范围里,倘若需要n+1位小数表示,并且是最小的小数的话,则应该是由n个0和最低位的1个1组成。也就是此时的尾数M = 1 + f = 1 + 2-n-1,由公式V=2E*M,使用阶码抵消掉小数位,则取阶码为2n+1,因此得到值为2n+1+1,即为n位浮点格式不能准确描述的最小正整数。
  • 结合例子理解一下
    实际上尾数决定了浮点数的精度,尾数只有23位,加上省略的小数点前的那位就是24位。如果一个int类型的值小于224,那么float是完全可以表示的。如果int类型大于224就不一定能表示了。如一个int数值的二进制表示形式是1000 0000 0000 0000 0000 0000,表示成指数形式是1.0000 0000 0000 0000 0000 000 * 223,对应的float的类型,尾数位全部为0,指数位是23+127=150,这样完全没有问题。假如一个int数值的二进制表示形式是10000 0000 0000 0000 0000 0001,表示成指数形式是1.0000 0000 0000 0000 0000 0001*224,对应的float的类型尾数位是0000 0000 0000 0000 0000 0001一共24位,这样就完全超出了float最多容纳23位尾数的能力。所以就不能正确表达这个int值了。由此也可以得出不能被float准确表达的最小int值是224+1。我们再将1 0000 0000 0000 0000 0000 0001的值加1,变成了1 0000 0000 0000 0000 0000 0010,这样变换为指数形式可以看出尾数又变为了23位,也就是说25位的二进制整数最后一位是0才能被float准确表示,每2个数就有一个不能被准确表示。如果是26位的二进制整数最后两位都是0才可以被float准确表达,每4个数就有3个不能被准确表示,以此类推。
  • 四种舍入方式:
    在这里插入图片描述
    • 向偶数舍入,即将数字向上或者向下舍入,使得结果的最低有效数字是偶数,主要用于将两个整数的中间值往偶数舍入,如2.5和1.5舍入后都是2。使用偶数舍入的原因是:如果计算这些值的平均数,向上舍入会导致平均值略高,向下舍入会导致平均值略低。
    • 向零舍入,即正数向下舍入,负数向上舍入。
    • 向下和向上舍入(略)
  • 偶数舍入法运用在二进制小数上:
    在这里插入图片描述
  • 例题:格式A转格式B:
    在这里插入图片描述
    在这里插入图片描述
  • 浮点运算的优势:IEEE标准中指定浮点运算行为方法的优势在于,它可以独立于任何具体的硬件或者软件实现。因此,我们可以检查它的抽象数学属性,而不必考虑它实际上是如何实现的。
  • 浮点加法乘法不具有结合性!!!
float x = 3.14;
float y = 1e10;
//x+y会将3.14舍入,结果为0
float sum1 = x + y - y;
//结果为3.14
float sum2 = x + (y - y);
  • C语言标准不要求机器使用IEEE浮点,所以没有标准的方法来改变舍入方式或者得到诸如-0,+∞,-∞或者NaN之类的特殊值。所以需要以下代码
//定义常数INFINITY(+∞)和NaN
#define _GNU_SOURCE 1
#include <math.h>
  • 自己宏定义,生成双精度值+∞,-∞和0
#define POS_INFINITY 1e400
#define NEG_INFINITY (-POS_INFINITY)
#define NEG_ZERO (-1.0/POS_INFINITY)
  • 当int、float和double进行强制类型转换时。

    • int->float:不会溢出,有可能舍入
    • int|float->double:没问题
    • double->float:可能会溢出为+∞
    • float->double->int:值会向零舍入
  • 整数转二进制和小数转二进制

    • 整数转二进制:
      正整数转二进制: 正整数转成二进制。要点一定一定要记住哈:除二取余,然后倒序排列,高位补零。

          21 /2    -------------------------------余  1
      
          10/2     -------------------------------余  0
      
           5/2      ----------------------------- 余  1
      
           2/2      ------------------------------余  0
      
           1/2     -------------------------------余  1
      

      记住,到着排序 10101 ,验证下转成十进制: 1×2的4次方+1×2的2次方+1×2的0次方=16+4+1=21。正确。
      计算机一般是8 位 16位 32位 64 位的,所以不够位高位补零。8位表示法:00010101

    • 小数转二进制:
      小数转换为二进制的方法:对小数点以后的数乘以2,有一个结果吧,取结果的整数部分(不是1就是0喽),然后再用小数部分再乘以2,再取结果的整数部分……以此类推,直到小数部分为0或者位数已经够了就OK了。
      演示:

               0.125×2=0.25 ........................0
      
               0.25×2=0.5...........................0
      
               0.5×2=1.0............................1
      

    即 0.125的二进制表示为小数部分为0.001
    记住,乘到小数为0。排序:正序。
    验证: 0.001 0×2的0次方+0×2的-1次方+0×2的-2次方+0×2的-3次方=1/8=0.125正确。
    由此可知,0.1用二进制表示是无穷的,需要舍入:

      			 0.1×2=0.2 .....................0
      			
      			 0.2×2=0.4 .....................0
      			
      			 0.4×2=0.8 .....................0
      			
      			 0.8×2=1.6......................1
      			
      			 0.6×2=1.2......................1
      			
      			 0.2×2=0.4......................0
      			 
      			 ... 
    

    得到0.1 = 0.00011001100110011…

补充:

  • 无符号数和补数的关系:
    x` = x + xw-12w
  • 使用mod2w将一个数截断成w位
  • 无符号和补码乘法的位级等价性:
    在这里插入图片描述
    在这里插入图片描述
发布了33 篇原创文章 · 获赞 3 · 访问量 620

猜你喜欢

转载自blog.csdn.net/qq_43647628/article/details/104494053