[csapp] 信息的表示和处理

这一章真是漫长的一章哇~我想一些东西不用在这里记录那么多。

十六进制和十进制的对应,记住A、C、F了就容易记了:
A - 10
C - 12
F - 15

数据类型的大小

单位:字节(Byte)8位1字节

C声明 32位机器 64位机器
char 1 1
short 2 2
int 4 4
long 4 8
long long 8 8
char * 4 8
float 4 4
double 8 8

简单总结:long类型和指针类型的大小是一致的,它们都与机器的位数一致;long long类型则是永远使用较大的位数(64位);其他数据类型32位机器与64位机器相同。

字节顺序

大端序 :低地址存高数据。
小端序 :低地址存低数据。

写数字时,左侧通常是高位;但列举寄存器时,左侧通常是低地址。
0x01234567
高位->低位

大端:

地址 0x100, 0x101, 0x102, 0x103
数据 01 23 45 67

小端:

地址 0x100, 0x101, 0x102, 0x103
数据 67 45 23 01

布尔运算

针对bit的运算(二进制0和1)
与、或、非、异或
&、|、~、^

逻辑运算

针对参数或表达式的运算(非零为TRUE,零为FALSE)
与、或、非
&&、||、!

移位运算

左移 :x << k 数值丢弃最高k位,低位补k个0
逻辑右移 :x >> k 数值高位补k个0
算术右移 :x >> k 数值高位补k个“最高有效位的值”

C语言中,对 无符号数 右移必是 逻辑右移 ;对 有符号数 常常使用 算术右移
Jave中,x >>> k 表示 逻辑右移 ;x >> k 表示 算术右移

整数的表示

无符号数 :1101B -> 1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = 13
有符号数 :有符号数的计算机表示方法时 补码 (two’s-complement),这个定义中二进制数字最高有效位解释为“负权”(为1就是负数)。 1101B -> -1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = -3

在这里,将一串二进制数作为“补码”处理时,最高位永远解释为“负”;所以,最高位是0,那这个数就是正数,最高位是1,因其权数最大,又被解释为“负”,那整个数字的值一定是负数。
另外,已知一个十进制负数,求它的补码,简单的算法是:负数的绝对值转为二进制数,在对这个二进制数求“补码”——即,取反加一;便可以得到原负数的计算机补码表示。

另外,可以注意到补码范围是不对称的(可在P38看到):最小值的绝对值,比最大值大1,即负数比正数多一个。原因是0的存在,它的最高位是0,划归为非负数,但它又不是正数;所以,负数比正数多一个。还有,最大的无符号数数值比补码最大值的两倍大1,原因同样。补码的表示中所有负数,以“无符号数”解释,都成为了正数。

对于有符号数,几乎所有机器都是使用 补码 的形式来表示的。有符号数还有两种表示方式: 反码 (Ones’ Complement)和 原码 (Sign-Magnitude),参见P43。
我想,“原码”“反码”和“补码”都是用于描述有符号数在计算机中的表示方法,对无符号整数谈论这三个表示方法没有什么意义。(而这可能是通常大学课程中的误区。)

有符号数和无符号数之间的转换 强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。

C语言中的隐式转换 :当执行运算是,如果它的的以运算数是有符号的,而另一个是无符号的;那么,C语言会 隐式地将有符号参数强制转换为无符号数数 ,并假设这两个数都是非负的,再来执行这个运算。
例如:-1 < 0U 。0U是无符号数,所以“-1”会被隐式转换为无符号数(保持位值不变,而是将其解释为无符号数);于是,实际运算的是:4294967295U < 0U 。这便与“本意”产生了差异。

较短数据类型转换为较长数据类型 将无符号数转换为一个更大的数据类型,只要在开头补0 (零扩展)。将一个补码数转换为一个更大的数据类型,则是开头全部补上原数字的最高有效位的值 (符号扩展)即最高位为1则全补1,最高位是0则全补0。

如果一个强制转换,同时涉及不同大小的数据类型和有符号数、无符号数。其转换规则是: 先改变数据类型大小,再从有符号转为无符号数。 而原较短数据类型的有符号数,在改变数据类型大小时,会进行“符号扩展”,扩展后的位值再解释为无符号数。
例如:

short sx = -12345;
unsigned uy = sx;

这里uy并不会变成“12345”,而是一个很大的数。实际的转换为 (unsigned) (int) sx;而不是 (unsigned) (unsigned short) sx。

另外留意书中P52和P53的练习2.25和2.26,描述了无符号数运算与有符号数比较,会产生的一些非直观的行为。

整数运算

没有细看。知道“乘以常数”,编译器采用了优化:一般使用移位(左移)和加法代替乘法。对除以2的幂,则是优化为右移,无符号数使用逻辑移位,补码数使用算术移位。

浮点数

这个比较复杂一点,在看这本书之前,曾经自己研究过浮点数的表示和误差的事情,待会把那时写的记录也发上来。
浮点数表示为: V = (-1)^S * M * 2^E
S:符号位 0为正,1为负
M:尾数位(书中称为“尾数”)范围[1, 2)
E:指数位(书中称为“阶码”)范围整数(可以为负)
这三个部分不是都可以简单直接填入浮点数的表示的。浮点数在计算机中的表示(float、double等):s-e-f。对于“e”和“f”,不同的类型有不同的计算方法。

规格化浮点数

通常的浮点数表示,能够表示出很大的数字。

s = S  
e = Bias + E  
f = M - 1  

Bias为偏执值,因为E可能是负数,而表示e是一个非负数,所以需要将E加上一个数将其变为非负。Bias的值与数据类型相关 Bias = 2 ^ (k - 1) - 1。对于float,指数位有8位(k = 8),Bias = 2^7 - 1 = 127。对于double,指数位有11位(k = 11),Bias = 2^10 - 1 = 1023。
对于M,它总是1 <= M < 2,首位总是1,所以我们将其减1,便额外获得一位精度。

非规格化浮点数

算是对“规格化浮点数”的补充,不能表示出较大的数,但是其值在0附近可以有很高的精确度。

s = S  
e = 全为0  
f = M  

在这里e的值和E无关,但是在我们转换为通常的浮点数值V时,依然需要E的值。E的值定义为 E = 1 - Bias。这样定义,可以让非规格化数平滑转变到规格化数,参见书中P72表格。
另外要提的是关于“0.0”,它是非规格化数,而且存在两个,正零(+0.0)和负零(-0.0)。

无穷大

s = S  
e = 全为1  
M = 全为0  

NaN (Not a Number)

s = S  
e = 全是1  
M = 不是全0  

舍入

很显然不是所有实数都可以表示为“V = (-1)^S * M * 2^E”,在有限的数据位中也不能表示无限二进制小数;所以就需要舍入操作。这里的舍入方法,我想,并不会在实际写程序中用到,这是在编译器以及浮点处理单元中要使用的。
浮点格式有四种舍入方式:

  1. 向偶数舍入 - 向偶数方向舍入
  2. 向零舍入 - 向零方向舍入
  3. 向下舍入 - 向负无穷方向舍入
  4. 向上舍入 - 向正无穷方向舍入

我对“偶数舍入”的理解:舍入计算分两步判断

  1. 原数字是否在中间点,如果不是,则四舍五入,结束。
  2. 如果在中间点,向偶数方向舍入,结束。

浮点运算

因为浮点数的精度和舍入问题,通常的数学计算规律在计算机浮点数运算上不一定成立。这个在写代码时要注意。
浮点数加法,满足交换律,不满足结合律。
浮点数乘法,满足交换律,不满足结合律;浮点乘法在加法上不满足分配律。

END

Written on November 5, 2019