Skip to main content

数制和编码

为什么计算机使用二进制编码​

  1. 二进制只有两种状态,元件制造成本低;
  2. 1 和 0 刚好提供了逻辑值“真”和“假”,方便实现逻辑运算;
  3. 编码和运算规则简单,方便通过逻辑门电路来实现。

进制转换​

进位计数法

常见的进位计数法有十进制、二进制、八进制、十六进制等,一般逢 10/2/8/1610/2/8/16 就进一位,这里的 10/2/8/1610/2/8/16 被称为基数。

一般来说,二进制转换到八进制或十六进制比较简单,因为 88、1616 都是 22 的倍数,只需要将二进制每三/四位分为一组,再将每组转换为对应进制的数即可。

对于其他进制而言,可以先将其转换为 1010 进制,再进行转换。

将整数从十进制转换到任意进制需要用到 除基取余法 ,即除以要转换进制的基数,得到余数,并对商再次进行同样的操作,直到商为 00,最后将余数按照 从下到上 的顺序排列即可,如求 2323 的二进制:

23÷2=11⋯111÷2=5⋯15÷2=2 ⋯12÷2=1 ⋯01÷2=0 ⋯1 \begin{align*} 23 \div 2 &= 11 \cdots 1 \\ 11 \div 2 &= 5 \cdots 1\\ 5 \div 2 &= 2\ \cdots 1\\ 2 \div 2 &= 1\ \cdots 0 \\ 1 \div 2 &= 0\ \cdots 1 \ \end{align*}

可以得到其二进制数为 1011110111.

对于小数,需要使用 乘基取整法,即乘以基数,取其整数部分,直到乘积为 1.01.0 或精度符合要求之后结束,最后按照 从上到下 的顺序排列。

warning

NN 位 1010 进制和 22 进制数分别能表示 10N,2N10^N,2^N 个数字,这也是计算机运算存在误差的原因,它表示仅有 0.2N0.2^N 的概率十进制数可以用二进制表示。

BCD 码​

二进制编码的十进制数(Binary−CodedDecimal, BCDBinary-Coded Decimal,\ BCD),采用 44 位二进制数来表示 11 位十进制数(24=16>102^4=16>10),此法可以使得二进制和十进制转换得以快速进行,但是有冗余状态。

常用 BCDBCD 码:

  1. 84218421 码,有权码,即 D=8b3+4b2+2b1+1b0D=8b_{3}+4b_{2}+2b_{1}+1b_{0} ,若两个 84218421 码相加小于等于 99 ,则不需要修正,若大于 99 ,则需要加 66 修正,因为落在这个区间内为无效码。
  2. 余 33 码:无权码,在 84218421 码的基础上加 (0011)2(0011)_{2} 形成的,因此每个数都多余 33,这样相加就不需要修正了。
  3. 24212421 码,有权码,即 D=2b3+4b2+2b1+1b0D=2b_{3}+4b_{2}+2b_{1}+1b_{0} ,特点是大于等于 55 的 44 位二进制数中最高位为 11,小于 55 的最高位为 00.

ASCLL 码​

ASCIIASCII 码使用指定的 77 位或 88 位二进制数组合来表示 128128 或 256256 种可能的字符。标准 ASCIIASCII 码也叫基础 ASCIIASCII 码,使用 77 位二进制数(剩下的 11 位二进制为 00)来表示所有的大写和小写字母,数字 00 到 99、标点符号,以及在美式英语中使用的特殊控制字符。其中:0∼310\sim31 及 127127 (共 3333 个) 是控制字符或通信专用字符,这些字符不可以显示。

常用的符号 ASCLLASCLL 码如下

符号编码
空格20H
131 H
A41 H
a61 H

汉字编码​

区位码:使用两个字节表示一个汉字,每个字节使用七位编码,将汉字和符号排列在一个表中,两个字节分别表示横纵坐标;

国标码:区位码的十六进制加上 2020H2020H,这是为了兼容不支持汉字的机器,防止被转换为 ASCLLASCLL 中的控制字符;

汉字内码:国标码的十六进制加上 8080H8080H,直接跳过 ASCLLASCLL 的范围。

汉字的内码有:

  1. GB2312-1980
  2. GBK
  3. GB18030-2000
  4. ISO/IEC10646-1、Unicode 2.0

定点数的编码表示​

根据小数点的位置是否固定,计算机中有两种数据格式:定点表示和浮点表示。

常用定点补码整数表示整数,定点原码小数表示浮点数的尾数部分,移码表示浮点数的阶码部分。

定点整数和定点小数没啥好说的,第一位是符号位,其他是数值位,但是注意小数转换为补码时,最后的 00 不需要变为 11,如 −0.110101∣000-0.110101\mid000 应该先对最后一个 11 以前的数取反 1.001010∣0001.001010 \mid000,即最后的 000000 不变,再加一得到 1.001011∣0001.001011\mid 000.

caution

定点整数/小数都是纯整数/小数,即只有整数/小数部分。

表示方法主要有 4 种:原码、补码、反码和移码。

原码:原码首位表示正负,其他位表示数值绝对值大小。

补码:原码的加减比较复杂,因此提出了补码来方便计算。 方式为 除了符号位按位取反再加 1,要求负数的原码只需要再次求补即可。

变形补码:又称 模4补码 ,即用两个二进制位来表示数字的符号位,其余与补码相同,用 0000 表示正,用 1111 表示负,用变形补码进行加减运算时,当运算结果的符号位出现 01/1001/10 ,则表示产生正/负溢。

caution

原码有两个 00,正零(00000000)和负零(10001000);补码只有一个 00(00000000),补码(10001000)表示的是 −1000-1000,即 −8-8;可以认为,在补码中,原来的负零被分配到表示最小的数去了。

反码:负数则数值位取反即可。

移码:移码常用来表示浮点数的阶数,且 只能用来表示整数。 移码就是真值加了一个常数 Y=X+2n,−2n≤X<2nY=X+2^n, -2^n\leq X<2^n,其中机器字节长为 nn,其和补码 符号位相反。

info

负数的反码/补码的数值部分越大/小,绝对值越小/大,真值越大/小,越靠近/远离 00.

运算方法和运算电路

基本运算部件​

计算机中运算器由 ALUALU、移位器、状态寄存器和通用寄存器组成。 ALUALU 的核心是加法器。

一位全加器​

全加器(FAFA)是最基本的加法单元,输入为两个加数和低位传来的进位,输出为一个结果和进位。

和表达式为 S=A⊕B⊕CS=A\oplus B\oplus C,进位表达式为 C=AB+(A⊕B)CC=AB+(A\oplus B)C

串行进位加法器​

将一些全加器串联可以得到串行进位加法器,每一级的进位依赖于前一级的输出,因此整个计算过程是串行的,严重影响计算时间。

并行进位加法器​

进位计算公式为

Ci=Gi+PiCi−1C_{i}=G_{i}+P_{i}C_{i-1}
info

其中 Gi=AiBi   Pi=Ai⊕BiG_{i}=A_{i}B_{i}\ \ \ P_{i}=A_{i}\oplus B_{i},分别表示生成(generategenerate)和传递(propagatepropagate),生成表示是否要产生进位,传递表示是否要将低位的进位传递给高位。若两个加数都为 11 时,会产生进位;若存在加数为 11,低位进位为 11 ,则也会产生进位。

以四位加法距离,进位公式为

C1=G1+P1C0C2=G2+P2G1+P2P1C0C3=G3+P3G2+P3P2G1+P3P2P1C0C4=G4+P4G3+P4P3G2+P4P3P2C1+P4P3P2P1C0\begin{align*} C_{1}&=G_{1}+P_{1}C_{0} \\ C_{2}&=G_{2}+P_{2}G_{1} + P_{2}P_{1}C_{0} \\ C_{3}&=G_{3}+P_{3}G_{2} + P_{3}P_{2}G_{1}+P_{3}P_{2}P_{1}C_{0} \\ C_{4}&=G_{4}+P_{4}G_{3} + P_{4}P_{3}G_{2}+P_{4}P_{3}P_{2}C_{1}+P_{4}P_{3}P_{2}P_{1}C_{0} \end{align*}

当位数太多时,电路会过于复杂,因此实际可以四位为一组,组内使用并行进位加法器,组间使用串行进位加法器或并行进位加法器(两级(多级)先行进位加法器)。

带标志加法器​

可以用于带符号整数的加减运算,需要额外处理标志位信息。

溢出标志 OF=Cn⊕Cn−1OF=C_{n}\oplus C_{n-1},符号标志为和的符号,零标志位 ZF=1ZF=1 当且仅当 F=0F=0,进位/借位标志 CF=Cout⊕CinCF=C_{out}\oplus C_{in}.

算术逻辑单元(ALU)​

info

ALUALU 可以实现加减乘除、与或非、移位等操作,ALUALU 的核心是带标志加法器。

ALUALU 接收两个 nn 位操作数,一个进位,和一个操作控制 ALUopALUop,用于控制计算类型。

定点数移位运算​

就是将数值整体左移或右移,超出的位数直接舍弃,并在空出的位置上添 00 或 11,循环移位就将超出的部分移动到空出的部分上。

算术移位​

算术移位的对象是有 符号数,移位过程中符号保持不变;左移表示乘二,右移表示除以二;

对于正数而言,原码、补码、反码的移位都是添 00;

对于负数,原码仍旧是添 00,反码添 11,补码左移添 00,右移添 11.

逻辑移位​

即无视符号位,直接移动。

循环移位​

循环移位分为 带进位标志位的循环移位(大循环) 和 不带进位标志为的循环移位(小循环),大循环就是进位标志位一起参与移位。

定点数的加减运算​

补码加减运算规则​

加法:补码直接相加,舍去高位。

减法:减数求负补码,转换为加法。

补码加减运算电路​

要实现减法,需要在加法器的输入前加 n−1n-1 位反向器进行取反,并使用一个二选一多路选择器控制,当信号 subsub 为 11 时,则认为做减法,二选一多路选择器选择取反的数据。 同时信号 subsub 一直作为进位传送给加法器,若是减法则起到取反之后加一的作用。

输出运算结果和标志位,零标志 ZF=1ZF=1 表示结果为 00;溢出标志 OFOF 对于有符号数表示溢出,对于无符号数无意义;符号标志 SFSF 表示结果正负,对于无符号数无意义;进/借位标志 CFCF 表示 无符号整数 的进位和借位,判断是否有溢出,做加法时,CF=1CF=1 表示产生进位,有溢出,实际输出不变,做减法时,CF=1CF=1 表示产生借位,有溢出,实际输出取反。

判别溢出​

一位符号位

V=AsBsS‾+AsBs‾SV=A_{s}B_{s}\overline S+\overline{A_{s}B_{s}}S

根据输入的符号位和结果的符号为进行判断,V=1V=1 则表示溢出。

也可以根据数据位的进位情况进行判断

若符号位的进位和最高位的进位相同,则无溢出

V=Cs⊕C1V=C_{s}\oplus C_{1}

双符号位

基于这样的一个事实:若两个 nn 位数字相加,则用 n+1n+1 位来表示则一定不会溢出。 若两位符号相同,则无溢出,若为 0101,则为正溢,1010,则为负溢。

进位判别法

最高位向符号位的进位 Cn−1C_{n-1} 和符号位向更高位的进位 CnC_{n},若不同,则发生溢出,可以表示为 VF=Cn−1⊕CnVF=C_{n-1}\oplus C_{n}.

定点数的乘除运算​

乘法运算由累加和右移操作实现,可以分为原码一位乘法和补码一位乘法。

原码一位乘法​

符号和数值分开求,符号运算使用异或 xs⊕ysx_{s}\oplus y_{s},每次判断乘数的最后一位,如果为 11,则加上被乘数,否则不加,再右移一位;循环直到移动了 nn 位,nn 为乘数的位数。

补码一位乘法​

如何根据补码快速求出其十进制

一个简单的方式是将最高位对应的十进制数值当作负数,其余位数的 11 当作正数,相加即可,如 1101,其原码是 1011,对应十进制是 −3-3,直接通过补码计算为 −8+4+1=−3-8+4+1=-3.

该原理基于补码的定义,因此可以推出负数补码的一般式,即最高为负数;再通过裂项,得到与最后两位的关系,这就是 booth 算法;通过该关系可以得到四种情况:

次低位最低位操作
00+0,右移一位
01+ [x] 补,右移一位
10+[-x] 补,右移一位
11+0,右移一位

一些细节如下:

  1. 乘数被乘数和运算结果都是补码;
  2. 部分积和被乘数 xx 使用双符号位,乘数 yy 使用单符号;
  3. 初始部分积为 00,并且需要在 yy 的末尾添加一个最低位 00;
  4. 累加 n+1n+1 次,右移 nn 次,nn 为 yy 的初始长度。

恢复余数法和加减法​

太麻烦了,略。

数据存储和排列​

大端方式:高位在前面

小端方式:低位在前面

数据以边界对齐的方式存储,如 32 位计算机,字地址一定是 4 的整数倍,若所存储数据不符合要求,会通过填充空白字节进行存储,可以提高读取指令和取数的速度。

浮点数的表示和运算

浮点数指小数点的位置可以浮动,这样在位数有限的情况下,扩大了数的表示范围,又保持了数的精度。

浮点数格式为

N=(−1)S×MREN=(-1)^S \times M R^E

其中 SS 用于控制正负;MM 为一个二进制定点小数,称为尾数,一般使用原码表示;EE 是一个二进制定点整数,称为 阶码或指数 ,RR 是基数,可以 约定 为 2,4,162,4,16 等,现在一般是 22.

info

尾数反应浮点数的精度,阶码反应浮点数的表示范围。

溢出

运算结果大于最大正数被称为正上溢,小于最小正数被称为负上溢,在 0 到最小正数之间被称为正下溢,在 0 到最大负数之间被称为负下溢。

note

由于 754 可以使用非规格化尾数来表示浮点数,因此其表示下界更大,这可以看作是一种 渐进下溢;再下溢可以当 0 处理。

规格化​

规格化的目的是为了充分利用尾数,使得有效数字能尽量占满尾数。 将尾数左/右移以达到尾数的最高位是有效数字的过程被称为左/右规。

info

规格化后的尾数大小应该满足 1R≤M<1\frac{1}{R}\leq M<1.

caution

基数不同,尾数规格化的形式也不同,如基数为 44,则最高 22 位不同时为 00.

IEEE 754 标准​

32 位:11 位符号、88 位阶码、2323 位尾数,阶码偏置值为 127127.

64 位:11 位符号、1111 位阶码、5252 位尾数。阶码偏置值为 10231023.

临时浮点数:11 位符号、1515 位阶码、6464 位尾数,总计 8080 位。 阶码偏置值为 1638316383.

warning

这里的移码并不符合一般一般移码的性质,一般移码的偏置值计算是 2n−12^{n-1},而 IEEE 754IEEE\ 754 中的阶码偏置值需要再减去 11: 2n−1−12^{n-1} - 1,这样 0000⋯(128)0000\cdots(128) 和 111⋯(−127)111\cdots(-127) 就可以被空出来表示其他作用了 (这段可能有问题),因此其移码的实际范围是 −2n−1+2∼2n−1−1-2^{n-1}+2\sim 2^{n-1}-1,如 −126∼127-126\sim 127.

若尾数为 00,当阶码全为 00 时表示正/负零,全为 11 表示正/负无穷;若尾数不为 00,而阶码全为 11,表示 NaNNaN,为 00 表示没有隐藏 11,是非规格化数。

隐含基数为 22.

对于规格化后的尾数,最高位总是 11 (若尾数使用补码表示,则需要分类讨论,若为负数,则最高位应该为 00,可以将逻辑化简为:符号位应该和最高位不同),因此将其再左移一位,可以将其隐藏,称为隐藏位,因此尾数实际有效位数会加 11.

bf16 和 tf32

浮点数 fp16fp 16 有 55 位阶码和 1010 位尾数,在深度学习领域经常会因为范围不够导致溢出,因此引入了 bf16bf16 用于表示更大的范围,其拥有 88 位阶码和 77 位尾数,并且其表示范围和 fp32fp 32 对齐,因此可以进行混合精度训练;

此外还有 tf32tf 32,其范围也和 fp32fp 32 对齐,使用相同的 88 位阶数,保持范围不变,而尾数则使用和 fp16fp 16 相同的 1010 位,可以良好的兼容二者,并且提高速度。

加减运算​

大致分为一下几个步骤:

  1. 对阶,使阶数相同,阶数小的尾数右移
  2. 尾数求和
  3. 规格化,这里的规格化需要加上隐藏位
  4. 舍入,对阶的过程中,将右移的尾数多保留两位,参与求和过程,最后对结果进行舍入
  5. 溢出判断
info

舍入的常见方法有:

  1. 00 舍 11 入法:保留的最高位为 11 则在尾数加 11,可能会需要再做一次右规。 若为 00 则舍去;
  2. 恒置 11 法:将右移后的尾数末尾置 11;
  3. 截断法:直接截取需要的位数;
  4. 就近舍入:舍入到最接近的可表示值,如果有两个最接近的值,则舍入偶数;
  5. 向正/负无穷方向舍入。

浮点数在数轴上的分布​

整数在数轴上是均匀分布的,但是浮点数的表示范围和阶码有关,而尾数表示的是精度,假设对于四位的尾数,则对于每一个阶码的取值,只有 23=82^3=8 种情况(第一位一定是 11),也即:

  1. 在 12n∼12n−1\frac{1}{2^n}\sim \frac{1}{2^{n-1}} 之间有 88 个数字。 . .
  2. 在 12∼1\frac{1}{2}\sim 1 之间有 88 个数字。 . .
  3. 在 2n−1∼2n2^{n-1}\sim 2^n 之间有 88 个数字。 . .

可以观察到,距离原点越近的位置数字越多,分布越集中,精度越高,但是在 22 的整数幂之间分布是均匀的。

浮点数的表示范围​

要求浮点数的表示范围,可以分为几个部分,一个是尾数所表示的范围,一个是阶码表示的范围,再配上符号即可。

以一个 1616 位的浮点数为例,其中阶码长度 66,尾数长度 99,符号位 11.

尾数的范围

warning

以下情况不包括隐藏位。

补码原码说明
最大正尾数0.111111111=1−2−90.111111111=1-2^{-9}0.111111111=1−2−90.111111111=1-2^{-9}全是 11 的情况
最小正尾数0.100000000=2−10.100000000=2^{-1}0.100000000=2−10.100000000=2^{-1}根据尾数的性质,必须大于等于 12\frac{1}{2}
最大负尾数1.111111111=−0.100000001=−(2−1+2−9)1.111111111=-0.100000001=-(2^{-1}+2^{-9})−0.100000000=−2−1-0.100000000=-2^{-1}补码的尾数取不到 −12-\frac{1}{2} 是因为 −0.1⋯-0.1\cdots 的补码是 1.10⋯1.10\cdots,不满足符号位和最高位不同的要求
最小负尾数1.000000000=−11.000000000=-1−0.111111111=−(1−2−9)-0.111111111=-(1-2^{-9})
info

对于有效数字位为 nn 位的定点小数(纯小数),若其全是 11,形如 0.11111⋯0.11111\cdots,则其对应的十进制为 1−2−n1-2^{-n},因为这个数再加上 0.000⋯10.000\cdots1 就会变成 11 .

阶码的范围

若根据 754 标准,阶码的偏移值是 26−1−1=312^{6-1}-1=31,而 66 位无符号定点整数表示的范围是 0∼630\sim 63,则阶码的范围是 −31∼32-31\sim 32(真值减去偏移值);而这里未作说明,因此可以使用 −32∼31-32\sim 31

表示范围

补码原码
最大正数(1−29)×231(1-2^9)\times 2^{31}(1−29)×231(1-2^9)\times 2^{31}
最小正数2−1×2−322^{-1}\times 2^{-32}2−1×2−322^{-1}\times 2^{-32}
最大负数−(2−1+2−9)×2−32-(2^{-1}+2^{-9})\times 2^{-32}−2−1×2−32-2^{-1}\times 2^{-32}
最小负数−231-2^{31}−(1−2−9)×231-(1-2^{-9})\times 2^{31}

C 语言中的类型转换​

这里只讨论隐式的类型转换,转换的基本原则是低精度类型向高精度类型转换,在运算过程中,有如下规则:

  1. 参与运算类型不同,则先转换为同一类型;
  2. 所有浮点运算都是以双精度计算,即使表达式只包含单精度,也会进行转换;
  3. charchar 和 shortshort 类型参与计算必须先转换为 intint;
  4. 赋值运算中会强制转换为左变量的类型。

在不同的系统中,C 语言中数据类型的长度是不同的 (单位:字节,一字节是四比特/位):

数据类型32 位系统64 位系统
short22
int44
long48
long long88

一些常用整数 (补码)的表示范围:

  1. int8 和 int16 的范围分别是 −128∼127-128\sim 127 和 −32768∼32767-32768\sim32767;
  2. uint8 和 uint16 的范围分别是 0∼2550\sim 255 和 0∼655350\sim 65535.

数据检错和纠错​

见计算机网络数据链路层。

总结和常见问题

浮点数阶码使用移码有什么好处​

  1. 比较大小更方便
  2. 检验特殊值(00 和无穷)方便

浮点数如何舍入​

舍入的原则是使得误差范围对称,平均误差为 0,防止误差累积。并且方法要简单,速度快。 IEEE 754IEEE \ 754 有 44 种舍入方法。

  1. 就近舍入,舍入为最近可以表示的数,若结果在两个可表示数中间,则选偶数
  2. 正向舍入,向正无穷方向舍入
  3. 负向舍入,向负无穷方向舍入
  4. 截去,向 00 方向舍入

现代计算机原码加减运算怎么实现​

IEEE 754IEEE\ 754 中,浮点数加减涉及到原码的运算,有两种方法

  1. 转换为补码运算后再转换回来
  2. 直接使用原码计算,符号和数值分开计算