# 2.3计算机中的二进制四则运算

## 阅读入口

* [二进制四则运算](/knowledge/cs/data-representation/binary-arithmetic/)
* [补码与带符号整数运算](/knowledge/cs/data-representation/twos-complement/)
* [浮点数与IEEE 754](/knowledge/cs/data-representation/floating-point/)

先修内容：[进制转换](/knowledge/cs/data-representation/number-base-conversion/)。

## 定长容器与无符号加法

$n$ 位**无符号整数**把全部位宽用于表示大小：第 $i$ 位（最低位为第0位）的权是 $2^i$，因此它表示 $0$ 到 $2^n-1$ 之间的全部整数，共 $2^n$ 个不同的值。位权与进制记号的由来见[进制转换](/knowledge/cs/data-representation/number-base-conversion/)。

|  位宽 |      最大值     | 常见对应     |
| :-: | :----------: | :------- |
|  8位 |     $255$    | 1字节      |
| 16位 |    $65535$   | 短整数      |
| 32位 | $4294967295$ | 单字（约42亿） |

下文用固定位宽书写，$(0101)_2$ 表示4位二进制数 $0101$；下标2是底数，不属于数值。

加法从最低位起逐位相加，**逢二进一**：

* $1+1=(10)_2$：本位写0，向高位进1；
* $1+1+1=(11)_2$：本位写1，向高位进1。

所以竖式中每一列的输入是“两个加数位 + 低位送来的进位”，输出是“本位和 + 向高位的进位”，一次最多进1。

[来源：二进制四则运算](/knowledge/cs/data-representation/binary-arithmetic/#definition)

本节从无符号加法起步，用位宽说明容器里的结果和真实值可以不同；后面的补码与浮点继续沿用定长字段。

## 补码

设有 $n$ 位二进制容器，整数 $x$ 的取值范围是 $-2^{n-1}\le x\le 2^{n-1}-1$。$x$ 的**补码**编码规定为：$x\ge 0$ 时与 $x$ 的无符号二进制相同；$x<0$ 时取 $2^n+x$ 的 $n$ 位二进制。因为 $2^n+x=2^n-|x|$，负数的编码相当于从 $2^n$ 中减去它的绝对值。

补码也可以按位权读：最高位的权是 $-2^{n-1}$，其余位的权仍是正的 $2^i$，即

$$
x=-b_{n-1}2^{n-1}+\sum_{i=0}^{n-2}b_i2^i .
$$

这解释了为什么最高位为1就代表负数，也说明最高位的权不是 $-1$ 而是 $-2^{n-1}$，这是按位读补码时最容易出错的地方。非负数的补码与无符号编码完全一致；同一串位按哪种规则解释，取决于变量的类型，而不是位串本身。

|  位宽 |             表示范围             |   编码个数   |
| :-: | :--------------------------: | :------: |
|  8位 |        $-128\sim 127$        |   $256$  |
| 16位 |      $-32768\sim 32767$      |  $65536$ |
| 32位 | $-2147483648\sim 2147483647$ | $2^{32}$ |

范围在负数一侧多一个数：$-2^{n-1}$ 有编码，而 $+2^{n-1}$ 没有。8位中`1000 0000`是 $-128$，`1111 1111`是 $-1$，`0000 0000`是0。无符号整数的位权与加法规则见[二进制四则运算](/knowledge/cs/data-representation/binary-arithmetic/)。

[来源：补码与带符号整数运算](/knowledge/cs/data-representation/twos-complement/#definition)

原码存在两个主要缺陷，补码与浮点的讨论从定长位串的解释规则出发。读定义时把注意力放在“同一串位按哪种规则解释”上。

## 浮点数的三段字段

二进制浮点数沿用科学计数法：先把数写成整数部分为一位的二进制小数，再乘以2的幂，即 $\pm(1.f)_2\times2^{e}$。IEEE 754把符号、指数、尾数三段装进定长字段，其中指数存成偏移后的 $E=e+127$（单精度），数值由下式还原：

$$
V=(-1)^S\times(1.f)_2\times 2^{E-127}.
$$

32位单精度的字段划分如下。

| 字段     |  位数 |    位置   | 含义               |
| :----- | :-: | :-----: | :--------------- |
| 符号 $S$ |  1  |   第31位  | 0为正，1为负          |
| 阶码 $E$ |  8  | 第30～23位 | 偏移后的指数，$E=e+127$ |
| 尾数 $f$ |  23 |  第22～0位 | 小数点后的部分，隐含前导1    |

写成位串时依次是`S EEEEEEEE FFF…F`，阶码在前、尾数在后。记尾数位为 $f_1f_2\cdots f_{23}$（$f_1$ 紧接小数点），则

$$
(1.f)_2=1+\sum_{i=1}^{23}f_i2^{-i},\qquad
V=(-1)^S\left(1+\sum_{i=1}^{23}f_i2^{-i}\right)2^{E-127}.
$$

这两个式子是换算的依据：先拆出三段，把阶码减127得到实际指数 $e$，再把尾数按 $2^{-1},2^{-2},\dots$ 展开，最后加上隐含的1。

[来源：浮点数与IEEE 754](/knowledge/cs/data-representation/floating-point/#definition)

浮点部分把同一思路推到实数：符号、阶码、尾数各占定长字段，换算时按定义里的公式逐段还原。

## 学习安排

教材先用无符号加法建立“位宽决定范围”的观念，再说明乘法、除法只是移位后相加或相减；接着用原码的两个缺陷引出补码；最后把“符号 + 阶码 + 尾数”的思想推广到浮点数。三部分共用同一条主线：$n$ 位容器只有 $2^n$ 个编码，所有规则都在回答这些编码分别代表什么数。

* 加法、乘法先按竖式手算，把进位与部分积对齐写清楚，再去读知识点里的性质。
* 补码先练8位正负转换，再练减法，最后单独做溢出判断。顺序颠倒容易把“进位”和“溢出”混为一谈。
* 浮点数只要求会拆32位单精度并套数值公式；双精度记住位宽与偏移即可。

## 课程约定

* 手算默认8位；题目给定4位或16位时以题目为准，位数不足在左侧补0。
* 写补码按4位一组留空格，例如`1111 1011`；填空要求“只填0和1”时不写空格和下标。
* 二进制竖式按位对齐，移位后的部分积直接写成移位后的数字，不再保留占位符。
* 教材只在精度对比时提到双精度，其余浮点例子都用32位单精度。

## 易错点

* 无符号加法丢掉最高位进位是回绕，属于溢出；补码减法丢掉进位却是正常结果。两处不要用同一句话记。
* 补码的表示范围不对称：8位是 $-128\sim127$，不是 $-127\sim127$。
* 判断补码溢出不能只看最高位有没有进位：8位中 $127+1$ 没有进位，结果却已经变成 $-128$。
* 阶码是偏移后的值，套公式前必须先减127；尾数小数点前还有一个不存储的1。
