Skip to content

数字与算数

原本这一章只是整理移位什么的。做 PA 做着发现我对数字的认识仍然非常浅薄,于是就整理这个。PA2 的实验记录尽快整理,(虽然)才完成一丢丢。

字节和字

一般的计算机都以字节为基本单位,1 字节 = 8 位。 PA 的存储模拟就是一个 char 型数组。

字是指针长度。比如 32 位就是 “字”。PA 的模拟器就是 32 位。

字节序

小端法和大端法。

小端法有利于加法,乘法,比大小。

https://www.ruanyifeng.com/blog/2022/06/endianness-analysis.html#comment-433763

整数表示

1. 无符号数

假设一个整数数据类型有 w 位。无符号数的每一位都用于构成这个数字。于是很显然,w 位可以表示的整数为 \([0, 2^{w}-1]\)

2. 有符号数

为了避免混淆,我们这里只说补码表示数。实际上在大多数情况下,计算机中存储的有符号数都是以补码的形式来存储的。(顺带,补码这个名字取得很烂,完全看不出它用的普遍性)

假设一个整数数据类型有 w 位。有符号数的最高位可以看成符号位,之后的 w - 1 位参与构成这个数字。于是,在非负数的情况下,最高位是 0,表示的整数可以在 \([0, 2^{w-1}-1]\) 之间。二进制来看是 \([(00..00)_2, (01..11)_2]\)

在负数的情况下,最高位是 1,表示 \(-2^{w-1}\)。于是负数的范围是 \([-2^{w-1}, -1]\)。二进制来看是 \([(10..00)_2, (11..11)_2]\)

综上,w 位有符号数表示的范围是 \([-2^{w-1}, 2^{w-1}-1]\)

怎么知道一个数的相反数对应的补码表示呢?太简单了,取反加一即可。举个例子:111010,取反加一得到 000110,很快就知道原来是 -6,得到的相反数是 6。

3. 有符号数与无符号数的转换

碰到这种问题时,不要想太多,从位的角度去考虑。

最简单的方法就是写出二进制表示,再转化成另一种类型,不展开说了。

数据在计算机中存储时,不管是以有符号数还是无符号数的形式读取,都不会改变数据本身。

但是,参与了计算以后,数据的类型会改变运算的结果。这里我们等会再说。

C 语言中的数

先说立即数吧。C 语言的立即数都是默认有符号的,比如 1234,声明一个无符号整数请带上后缀 "u", 比如 1234u

变量的情况:C 语言中有符号数和无符号数相互转化时,保持底层的位不变。于是下面的代码:

int32_t x = -1;
printf("x = %u = %d\n", x, x);

输出 x = 4294967295 = -1

运算的情况下,若二元运算符的一个运算数有符号,还有一个运算数无符号,C 语言会将所有数字转化为无符号数。

练习题:

最经典的就是 -1>0u 这个表达式结果为真,你知道为什么吗。

2147483647>(int)2147483648u,为什么为真?

数字扩展和截断

扩展实在是太简单了,对无符号数,左边加 0 即可。有符号数则是左边添符号位。

截断:再说。

位运算

其实 C 语言中的位运算还挺麻烦的, 因为没办法直接置位清零.

移位运算

左移右移。注意算数右移和有符号扩展对应,逻辑右移和无符号扩展对应。

下面讲一些应用.

将 value 的第 position 位置成 bitValue 的值. 方法是先清零, 再进行或运算.

value = (value & ~(1 << position)) | (bitValue << position);

整数运算

无符号加法

CSAPP 讲的有些抽象。两个无符号数进行加法或者减法,直接进行加法或者减法,然后在把结果转化成对应的无符号数即可。

比如对 32 位 1-2=-1=4294967295

大数相加可能会溢出,溢出去掉多出来的 1 即可,比如对 4 位,9+12=21=5.

有符号加法

对于有符号数,只考虑加法就可以了,因为减去一个数相当于加上一个负数。两个数的补码相加,就和无符号数加法一模一样,加起来,舍去进位即可。

正数加负数不会溢出,溢出的情况可能是正数加正数或负数加负数。

浮点数

浮点数现在已经有了统一的表示方式,即 IEEE-754。我们以 64 位双精度浮点数为例进行讲解。

任何的浮点数 \(x\) 都可以按照下面的式子表示:

\[ x = (-1)^s\cdot(1+f)\cdot2^e \]

其中 \(s\) 是符号位,决定浮点数的正负。\(f\) 是小数,满足 \(0\leq f <1\),决定浮点数的精度。\(e\) 是指数,满足 \(-1022\leq e\leq 1023\),决定浮点数的范围。看这个式子,实际上就是二进制下的科学记数法。

\(x\) 被存储在一个 64 位的字中,1 位符号位 \(s\),11 位存 \(e\),52 位存 \(s\),如下图所示。

+----------------+----------------+----------------------------------------------------+
| 符号位 (1 bit) | 指数 (11 bits) | 尾数 (52 bits)                                     |
+----------------+----------------+----------------------------------------------------+

\(f\) 首位的 1 不需要存储。指数 \(e\) 存储的是 \(e+1023\) 偏移值,保证指数是正数。

下面,举两个实际的例子看如何将数字转换成浮点表示。

\(-12.25\):是负数,所以 \(s=1\)。再将 \(12.25\) 转化成二进制表示,\(12.25 = 1100.01\)。(读者自行思考如何转换小数)。然后将移动小数点,将整数部分变为 \(1\)——这就是浮点数名称的由来,结果是 \(1.10001*2^3\)。到这里,指数,小数,和负号就都知道了。要存储的就是 \(s=1\)\(e=3+1023=1026\),小数部分 \(f=10001\),那么\(-12.25\) 这个数的浮点表述就是:

+----------------+----------------+----------------------------------------------------+
| 符号位 (1 bit) | 指数 (11 bits) | 尾数 (52 bits)                                     |
+----------------+----------------+----------------------------------------------------+
| 1              |  10000000010   | 1000100000000000000000000000000000000000000000000000 |
+----------------+----------------+----------------------------------------------------+

\(0.1\)\(0.1\)是正数,但是它在二进制中是一个无限循环小数,\(0.1=0.000110011001100\cdots\)。同样,我们用二进制科学记数法表示:\(1.10011001\cdots * 2^{-4}\),要存储的就是 \(s=0\)\(e=-4+1023=1019\),小数部分 \(f=10011001\cdots\),直到被截断(舍入)。那么就是:

+----------------+----------------+----------------------------------------------------+
| 符号位 (1 bit) | 指数 (11 bits) | 尾数 (52 bits)                                     |
+----------------+----------------+----------------------------------------------------+
| 0              | 01111111011    | 1001100110011001100110011001100110011001100110011010 |
+----------------+----------------+----------------------------------------------------+

有个重要的值,\(\mathrm{eps}\),其定义了浮点数的精度,实际上就是 f 所能达到的最小值:

\[ \mathrm{eps = 2^{-52} = 2.220446*10^{-16}} \]

当然,浮点数能达到的最小绝对值是\(2^{-1022}\)。每一个二进制区间 \([2^e, 2^{e+1}]\) 中的精度是相同的,均为 \(2^e\cdot \mathrm{eps}\)。思考一下就可以知道,绝对值越小,精度越高。

浮点数的四则运算,对加法和减法来说,要将两数的指数化成一致,再对小数进行加减法运算,最后将计算结果化成标准形式。乘除则加减指数,对小数部分进行乘除法运算。