计算机只能存储 0 和 1。为了表示正数、负数和小数,需要事先约定符号位、小数点位置以及编码规则。
其中最重要的四种编码是:原码、反码、补码和移码。

真值、机器数与定点数

真值与机器数

真值是数在数学意义上的实际值,例如 +5+5、−5-5、+0.75+0.75、−0.25-0.25。

机器数是该数在计算机中的二进制编码。计算机不能直接存储正负号,因此通常用最高位表示符号:

  • 00 表示正数;
  • 11 表示负数。

例如,在 8 位原码中:

(00000101)原=+5,(10000101)原=−5(00000101)_{\text{原}}=+5,\qquad (10000101)_{\text{原}}=-5

重点: 真值是数学意义上的数,机器数是按照特定编码规则得到的二进制表示。同一个真值采用不同编码时,机器数可能不同。

以 −5-5 为例:

编码方式 8 位机器数
原码 10000101
反码 11111010
补码 11111011
移码 01111011

因此,看到机器数 1111101111111011 时,必须先明确它采用的是哪一种编码,才能确定真值。

定点整数与定点小数

定点数的“小数点”位置是事先约定的,机器中通常不实际存储小数点。

  • 定点整数: 小数点固定在数值位的最右侧,如 x7x6⋯x0.x_7x_6\cdots x_0.。
  • 定点小数: 小数点固定在符号位之后,如 x0.x1x2⋯x7x_0.x_1x_2\cdots x_7。

例如,若采用定点小数格式,则:

0.1012=12+18=0.625100.101_2=\frac12+\frac18=0.625_{10}

与定点数相对的是浮点数,其小数点位置可以随阶码变化。

下文若无特别说明,统一采用 8 位定点整数:最高位为符号位,其余 7 位为数值位。

原码

原码由“符号位 + 绝对值的二进制表示”组成:正数符号位为 00,负数符号位为 11。

例如:

[+5]原=00000101,[−5]原=10000101[+5]_{\text{原}}=00000101,\qquad [-5]_{\text{原}}=10000101

读取原码时,先看符号位,再把其余位按无符号数解释。例如:

(10001101)原=−13,(00001101)原=+13(10001101)_{\text{原}}=-13,\qquad (00001101)_{\text{原}}=+13

原码的特点

8 位原码有两个零:

[+0]原=00000000,[−0]原=10000000[+0]_{\text{原}}=00000000,\qquad [-0]_{\text{原}}=10000000

由于 1 位用于表示符号,7 位数值位最大只能表示 127127,因此其范围为:

−127≤x≤127\boxed{-127\le x\le127}

原码直观,但不适合直接进行加减运算。例如:

00000101+10000011=1000100000000101+10000011=10001000

若把结果仍按原码解释,会得到 −8-8,而实际 5+(−3)=25+(-3)=2。这说明原码运算还需要额外处理符号和绝对值。

反码

反码的规则是:

  • 正数的反码与原码相同;
  • 负数的反码保持符号位为 11,其余数值位全部取反。

例如:

[−5]原=10000101[-5]_{\text{原}}=10000101

数值位 00001010000101 取反后得到 11110101111010,所以:

[−5]反=11111010[-5]_{\text{反}}=11111010

也可以从对应正数的编码出发,将所有位取反:

[+5]反=00000101→全部取反[−5]反=11111010[+5]_{\text{反}}=00000101 \xrightarrow{\text{全部取反}} [-5]_{\text{反}}=11111010

反码仍然有两个零:

[+0]反=00000000,[−0]反=11111111[+0]_{\text{反}}=00000000,\qquad [-0]_{\text{反}}=11111111

因此,8 位反码的表示范围仍为:

−127≤x≤127\boxed{-127\le x\le127}

补码

现代计算机通常使用补码保存和运算有符号整数。

补码的规则是:

  • 正数的补码与原码、反码相同;
  • 负数的补码等于其反码加 11。

例如:

[−5]原=10000101[−5]反=11111010[−5]补=11111010+1=11111011\begin{aligned} [-5]_{\text{原}}&=10000101\\ [-5]_{\text{反}}&=11111010\\ [-5]_{\text{补}}&=11111010+1=11111011 \end{aligned}

负数求补码也可以记为:对应正数的各位全部取反,再加 11。例如:

00001101→取反11110010→+11111001100001101\xrightarrow{\text{取反}}11110010\xrightarrow{+1}11110011

所以:

[−13]补=11110011[-13]_{\text{补}}=11110011

由补码求真值

若补码最高位为 00,它表示非负数,可以直接读取。若最高位为 11,则对机器数取反加 11,得到其绝对值,再添加负号。

例如:

11111011→取反00000100→+10000010111111011\xrightarrow{\text{取反}}00000100\xrightarrow{+1}00000101

因此:

(11111011)补=−5\boxed{(11111011)_{\text{补}}=-5}

补码的位权解释

对于 8 位补码 b7b6⋯b0b_7b_6\cdots b_0,最高位的权值为 −27-2^7,其余位的权值仍为正:

x=−b727+b626+b525+⋯+b020x=-b_7 2^7+b_6 2^6+b_5 2^5+\cdots+b_0 2^0

例如:

(11111011)补=−128+64+32+16+8+2+1=−5\begin{aligned} (11111011)_{\text{补}} &=-128+64+32+16+8+2+1\\ &=-5 \end{aligned}

这种方法也能解释特殊编码:

(10000000)补=−128\boxed{(10000000)_{\text{补}}=-128}

不能把它按照“符号位 + 后 7 位绝对值”的原码规则读取。

补码的优势

补码可以把减法转换成加法。例如,计算 5+(−3)5+(-3):

[5]补=00000101,[−3]补=11111101[5]_{\text{补}}=00000101,\qquad [-3]_{\text{补}}=11111101

直接相加:

+00000101+111111011 00000010\begin{array}{r} \phantom{+}00000101\\ +11111101\\ \hline 1\,00000010 \end{array}

机器字长只有 8 位,丢弃最高位进位后得到 0000001000000010,即 22。

补码还把正零和负零统一为同一个编码:

[0]补=00000000[0]_{\text{补}}=00000000

原本用于表示“负零”的编码位置可用于表示 −128-128,所以 8 位补码的范围为:

−128≤x≤127\boxed{-128\le x\le127}

一般地,nn 位补码的范围为:

−2n−1≤x≤2n−1−1\boxed{-2^{n-1}\le x\le2^{n-1}-1}

移码

移码通常用于表示浮点数的阶码。它通过加入固定偏置,把有符号数映射为无符号编码,从而便于比较大小。

本文采用 8 位移码、偏置量为 27=1282^7=128:

[x]移=x+128\boxed{[x]_{\text{移}}=x+128}
真值 加入偏置后的值 8 位移码
-128 0 00000000
-5 123 01111011
-1 127 01111111
0 128 10000000
+1 129 10000001
+5 133 10000101
+127 255 11111111

在这种偏置约定下,同一个数的移码等于其补码的最高位取反,其余位保持不变:

[−5]补=11111011→最高位取反[−5]移=01111011[-5]_{\text{补}}=11111011 \xrightarrow{\text{最高位取反}} [-5]_{\text{移}}=01111011

移码按无符号数比较时,编码顺序与真值大小顺序一致:

01111011<10000000<1000010101111011<10000000<10000101

对应:

−5<0<5-5<0<5

注意: “移码等于补码符号位取反”只适用于偏置为 2n−12^{n-1} 的 nn 位移码。IEEE 754 的阶码采用偏置表示,但其偏置值和特殊编码规则需要单独判断。

定点小数的编码

定点小数与定点整数采用相同的编码思想,区别只在于小数点的位置。

假设格式为 x0.x1x2x3x4x_0.x_1x_2x_3x_4,其中最高位 x0x_0 是符号位。因为:

0.62510=0.10120.625_{10}=0.101_2

所以:

[+0.625]原=0.1010[+0.625]_{\text{原}}=0.1010

对于 −0.625-0.625:

[−0.625]原=1.1010[−0.625]反=1.0101[−0.625]补=1.0110\begin{aligned} [-0.625]_{\text{原}}&=1.1010\\ [-0.625]_{\text{反}}&=1.0101\\ [-0.625]_{\text{补}}&=1.0110 \end{aligned}

四种编码对比

以 8 位定点整数为例:

真值 原码 反码 补码 移码
+5 00000101 00000101 00000101 10000101
-5 10000101 11111010 11111011 01111011
+0 00000000 00000000 00000000 10000000
-0 10000000 11111111 无单独编码 无单独编码
编码 8 位整数范围 主要特点
原码 −127∼127-127\sim127 直观,但加减运算不便,有两个零
反码 −127∼127-127\sim127 负数数值位取反,仍有两个零
补码 −128∼127-128\sim127 加减运算统一,零的编码唯一
移码 −128∼127-128\sim127 便于按无符号编码比较大小

对于非负数:

原码=反码=补码\boxed{\text{原码}=\text{反码}=\text{补码}}

在本文的移码约定下,负数的编码转换关系为:

原码→数值位取反反码→+1补码→最高位取反移码\boxed{ \text{原码} \xrightarrow{\text{数值位取反}} \text{反码} \xrightarrow{+1} \text{补码} \xrightarrow{\text{最高位取反}} \text{移码} }

重点: 实际学习和运算中最重要的是补码。要特别记住补码的求法、真值还原、表示范围,以及最高位具有负权这一点。


嘻嘻,美好的周末结束了。
痛苦的上学日又要来了,
如果学校消失,可能更有利于我学习。
今天就不看飞天小女警了,早点睡觉,
留着明天看。😇
中岛由贵