CS:APP 第二章学习笔记

CS:APP 第二章 Representing and Manipulating Information 的学习笔记

这章的主要内容为 byte整数和浮点数的存储及计算

Information Storage

byte 而非 bit 是 memory 的基本单位memory 可以看作 array of bytes

一个 byte 常用两个十六进制数码表示

word size 表示 virtual memory 的位数大小所以也是指针的位数在 C 语言中word size 也会影响到整型的大小

Byte Ordering

很多时候单个数据需要用多个 byte 表示于是就有两种可能的 byte ordering: big endian 和 little endian

如果把每个 byte 看作一个数位而把由多个 byte 组成的单个数据看作一个256 进制数那么 big endian 就是从高位开始书写little endian 就是从低位开始书写也就是说big endian 看上去是更加符合人类的书写习惯而 little endian 则像是把 1234 写成 4321但是little endian 会把低位存在低地址从这个角度来说又更加自然一些

由于 byte 是 memory 的基本单位endian 影响的是 byte 的排列顺序而不是 bit 的排列顺序如果把一串 byte 分别用 big endian 和 little endian 写出来例如 big endian 0x1234little endian 0x3412可能会感觉 endian 不影响byte 内部的顺序很奇怪但其实 0x120x34 只是 byte 的一种表示方式并不代表byte 内部的顺序上面说把由多个 byte 组成的单个数据看作一个256 进制数也是考虑到 10 进制数的反过来写是为人熟知的这样的话用256 进制数来作类比会比较好理解且不容易误解

一般来说byte ordering 对于程序员来说是无关紧要的但是如果要将数据与外界分享例如通过网络传输或者需要查看原始的 byte array在 machine-level program 中或者需要通过类型转换union 等方式绕开 C 语言的类型系统则 byte ordering 会非常重要

字符串的表示是不受 byte ordering 影响的

位运算

主要是左移右移其它是熟知的

左移时高位会被丢弃低位会填充零

右移则有两种: 逻辑右移和算术右移两者都是丢弃低位但逻辑右移是高位填零算术右移是高位填原来最高位的值

在 C 语言中unsigned integer 一定是逻辑右移而 signed integer 则一般是算术右移

算术右移的行为是为了在使用补码表示负数时得到正确的结果

在位移过大超过 word size行为是不确定的但一般会将位移对 word size 取模

Integer Representations

整型编码

unsigned integer 的编码就是普通的二进制

signed integer 一般采用补码 (twos-complement encoding)即在 word size 为 ww 时最高位表示 2w1-2^{w-1} 而非 2w12^{w-1}也就是说最高位为 0 表示非负为 1 表示负数在最高位为 0 时和 unsigned integer 是一致的而在最高位为 1 时是同样编码的 unsigned integer 减去 2w2^w

signed unsigned 转换

在 C 语言中在同样长度的 signed 和 unsigned 之间转换时虽然不一定但一般是不改变编码地只进行类型的转换

若算术运算符的两侧分别是 signed 和 unsigned则会将 signed 隐式转换为 unsigned这在运算符为比较运算符时尤其可能导致意外的结果例如 -1 > 0u

整型增长

将某个长度的 unsigned integer 转换为更长的 unsigned integer 时会在高位补零

将某个长度的 signed integer 转换为更长的 signed integer 时会在高位补原来的最高位类似于 arithmetic right shift以保证转换后数值不变

如果一个类型转换既要增长又要转变 signed/unsigned会先转换长度再转换 signed/unsigned

整型缩短

无论是 signed 还是 unsigned在缩短整型时会直接将高位截去在新的整型长度为 ww这相当于对 2w2^w 取模

Integer Arithmetic

加法和取反

unsigned integer 的加法2w2^w

判断 unsigned integer 加法是否发生溢出x + y 溢出当且仅当 x + y < x

unsigned integer 取反x{0x=02wxx>0x \mapsto \begin{cases} 0 & x = 0 \\ 2^w - x & x > 0 \end{cases}

取反有两种计算方式

  • 按位取反后加一
  • 或者找到二进制表示中最低位的 1 然后将比这一位高的位取反

signed integer 的加法把编码当作 unsigned integer 算就可以实现 positive overflow 和 negative overflow 的效果即 overflow 后保持模 2w2^w 不变

判断 signed integer 加法是否发生溢出x + y positive overflow 当且仅当 x > 0 && y > 0 && x + y <= 0negative overflow 当且仅当 x < 0 && y < 0 && x + y >= 0

signed integer 取反: 把编码当作 unsigned integer 来取反即可表现为能表示的最小值取反得到自身其它值取反就是其相反数

乘法

无论 signed integer 还是 unsigned integer乘法都是丢弃高位即模 2w2^w且在编码上是等价的

如果乘法运算中的某个因数是常数编译器可能会把乘法优化为位移和加法的组合是否以及如何进行优化取决于常数的值以及相关指令加法位移乘法可能还有 LEA 等指令的相对速度与具体机器密切相关

除以 2 的幂

如果除法运算中除数是 2 的幂编译器会将除法优化为右移

总结

计算机的整数运算总的来说通过取模来处理溢出而使用补码表示 signed integer 可以使 signed integer 和 unsigned integer 的运算在编码层面上的实现相同

Floating Point

IEEE 浮点表示

浮点数大体上是一个二进制的科学计数法形如 (1)s×M×2E(-1)^s \times M \times 2^E

IEEE 浮点表示的编码包含三部分

  1. sign bit表示 ss
  2. exponent field表示 EE下文中记其表示的 unsigned integer 为 ee
  3. fraction field表示 MM下文中记 fraction field 为 fn1f1f0f_{n-1} \cdots f_1 f_0

如果简单地使用普通整数的表示法来表示 MMEE会遇到一些问题

  • EE 需要能是负数以用来表示比较小的数
  • M=0.fn1f1f0M = 0.f_{n-1} \cdots f_1 f_0那么
    • EE 不同的编码可能表示同一个值造成编码的浪费
    • 可能会出现 EE 更大但值更小的情况会给比较两个数造成困难
  • 不能表示 ±\pm \inftyNaN 等特殊值

为了解决这些问题IEEE 浮点表示采取了如下措施

首先浮点数被分为三类normalized values, denormalized values 和 special values

Normalized values:

  • 一个浮点数是 normalized value当且仅当其 exponent field 既不全零也不全一
  • E=eBiasE = e - \mathrm{Bias}其中 Bias\mathrm{Bias} 是一个预先设置的常量
  • M=1.fn1f1f0M = 1.f_{n-1} \cdots f_1 f_0

Denormalized values:

  • 一个浮点数是 denormalized value当且仅当其 exponent field 全零
  • E=1BiasE = 1 - \mathrm{Bias}
  • M=0.fn1f1f0M = 0.f_{n-1} \cdots f_1 f_0

Special values:

  • 一个浮点数是 special value当且仅当其 exponent field 全一
  • 若 fraction field 全零则根据 sign bit 表示 ±\pm \infty
  • 若 fraction field 非全零则表示 NaN

现在来看这套编码规则如何解决了上面提出的问题

  1. 设置 Bias\mathrm{Bias}以表示小于零的 EE
  2. 将 normalized values 的 MM 的最高位钦定为 1以避免不同 EE 表示同一个数这和标准的十进制科学计数法要求小数在 [1,10)[1, 10) 的范围内是一个道理在二进制科学计数法中就是要求小数在 [1,2)[1, 2) 的范围内
  3. 由于 EE 的取值范围有限normalized values 的 MM 最高位强制为 1 使其无法表示 0.00.0 以及接近 00 的数能够表示 2Bias2^{-\mathrm{Bias}}(1+ε)×2Bias(1 + \varepsilon) \times 2^{-\mathrm{Bias}}却无法表示位于 [0,2Bias)[0, 2^{-\mathrm{Bias}}) 内的数所以增设 denormalized values 这一分类用来表示接近 00 的数以及 ±0.0\pm 0.0由于 denormalized values 没有将 MM 的最高位设为 11它的 EE 设置为 1Bias1 - \mathrm{Bias} 而非 Bias-\mathrm{Bias} 作为补偿
  4. 通过 special values 的分类表示了 ±\pm \infty 以及 NaN
  5. 使用 exponent fields 按照 denormalized -> normalized -> special 的顺序进行分类对于同一符号的浮点数只需将 exponent field 和 fraction field 看作无符号整数即可比较大小实际比较时除了先比较符号位可能还要考虑 ±0.0\pm 0.0NaN 等特殊情况

IEEE 浮点表示还规定了每个 field 的长度

  • 32 位: exponent field 8 位fraction field 23 位
  • 64 位: exponent field 11 位fraction field 52 位

记 exponent field 的位数为 kkBias=2k11\mathrm{Bias} = 2^{k-1} - 1即 32 位为 12712764 位为 10231023

特殊标志性的浮点数

exponent field 和 fraction field 全零表示 ±0.0\pm 0.0

exponent field 全零fraction field 最低位 1 其它位 0是能够表示的最接近零的数32 位约为 1.4×10451.4 \times 10^{-45}64 位约为 4.9×103244.9 \times 10^{-324}注意这个数的值为 222kn2^{2-2^k-n}ε=2n\varepsilon = 2^{-n} 不同

exponent field 最高位 0 其它位 1这得益于 Bias\mathrm{Bias} 的设定fraction field 全零表示 ±1.0\pm 1.0

exponent field 最低位 0 其它位 1fraction field 全一是能够表示非 special value的最大的数32 位约为 3.4×10383.4 \times 10^{38}64 位约为 1.8×103081.8 \times 10^{308}

浮点数舍入

浮点数的舍入有四种模式可供选择

  • round-to-even: 类似于四舍六入五成双但是二进制是默认的舍入模式
  • round-toward-zero
  • round-down
  • round-up

浮点数运算

除了一些特殊值sqrt(-1.0)1/0.0浮点数的运算结果被定义为精确计算后进行舍入得到的结果但具体计算的实现方式是随意的不需要真的先精确计算再进行舍入

浮点数的加法和乘法会进行舍入可能溢出所以不满足结合律分配律但是满足交换律调换结合顺序可能改变计算结果意味着编译器无法以改变结合顺序为代价进行优化

C 语言中浮点数类型转换

简单来说整数转成浮点数或不同类型的浮点数之间进行转换可能舍入也可能溢出浮点数转成整数会向零取整溢出时行为不确定