3976 字
20 分钟
2.1数制和编码

2.1.1 进位计数制及其相互转换#

在计算机系统内部,所有信息均采用二进制进行编码,主要原因如下。

  1. 二进制只有两个状态,只需使用具有两种稳定物理状态的器件即可表示每一位,硬件实现成本较低。例如,可用高电平和低电平分别表示1和0。

  2. 二进制的1和0恰好对应逻辑值“真”与“假”,为计算机实现逻辑运算和程序中的条件判断提供了直接支持。

  3. 二进制的运算规则极为简单,可通过基本的逻辑门电路高效实现各类算术与逻辑操作。

1. 进位计数制#

常用的进位计数制包括十进制、二进制、八进制和十六进制。十进制是日常生活中最常用的计数制,而计算机内部主要使用二进制,并常借助八进制和十六进制来简化表示。

在进位计数制中,基数是指每个数位所能使用的不同数码的个数。例如,十进制的基数为10(数码为0~9),计数时遵循“逢十进一”的规则。以十进制数101为例,百位的1表示100,个位的1表示1,二者数值不同,是因为每一位的实际值等于该数码乘以其所在位置的位权。一个进位制数的数值,等于各位数码与其位权的乘积之和。

一个 rr 进制数 (KnKn1K0K1Km)(K_nK_{n-1}\cdots K_0K_{-1}\ldots K_{-m}) 的数值可表示为

Knrn+Kn1rn1++K0r0+K1r1++Kmrm=i=nmKiriK_nr^n+K_{n-1}r^{n-1}+\cdots+K_0r^0+K_{-1}r^{-1}+\cdots+K_{-m}r^{-m}=\sum_{i=n}^{-m}K_ir^i

式中,rr基数rir^i 是第 ii 位的位权KiK_i 是第 ii 位的数码,取值范围为 0,1,,r10,1,\cdots,r-1

  1. 二进制。基数为2,数码为0和1,计数“逢二进一”。第 ii 位的位权为 2i2^i

  2. 八进制。基数为8,数码为0~7,计数“逢八进一”。由于 8=238=2^3,每3位二进制数恰好对应1位八进制数,两者转换十分便捷。

  3. 十六进制。基数为16,数码为0~9和 AFA\sim F (AFA\sim F 分别代表10~15),计数“逢十六进一”。由于 16=2416=2^4,每4位二进制数对应1位十六进制数,转换同样便捷。

为便于区分,常在数字后添加后缀字母来标识进制:BB 表示二进制数,OO 表示八进制数,DD 表示十进制数(通常省略),HH 表示十六进制数;此外,也常用前缀 0x 表示十六进制数。

2. 不同进制数之间的相互转换#

(1) 二进制数转换为八进制数和十六进制数#

对于一个既有整数部分又有小数部分的二进制数,转换时以小数点为界分别处理:整数部分 ,从小数点向左,每3位(八进制)或每4位(十六进制)分为一组,若最左侧不足3位或4位,则在高位补0;小数部分 ,从小数点向右,同样每3位或4位分为一组,若最右侧不足,则在低位补0。分组完成后,将每组直接替换为对应的八进制或十六进制数码即可。

反之,将八进制数或十六进制数转换为二进制数时,只需将每位数码分别替换为对应的3位或4位二进制数(必要时去掉整数最高位或小数最低位的0)。八进制数与十六进制数之间的转换,通常先转换为二进制数,再转为目标进制,这是最直接且不易出错的方式。

(2) 任意进制数转换为十进制数#

采用按权展开相加法,将各位数码和对应的位权(基数的幂次)相乘,再求和。

例如,(11011.1)2=1×24+1×23+0×22+1×21+1×20+1×21=27.5(11011.1)_2=1\times2^4+1\times2^3+0\times2^2+1\times2^1+1\times2^0+1\times2^{-1}=27.5

(3) 十进制数转换为任意进制数#

通常采用基数乘除法,对整数部分和小数部分分别处理:

  1. 整数部分使用除基取余法:不断除以目标进制的基数,记录余数,直至商为0;最先得到的余数为最低位,最后得到的为最高位

  2. 小数部分使用乘基取整法:不断乘以基数,记录整数部分,直至小数部分为0或达到所需精度。最先得到的整数为最高位,最后得到的为最低位

最终将两部分的转换结果拼接,即得到目标进制数。

NOTE

关于除基取余法和乘基取整法的原理,建议结合 rr 进制数的数值定义公式理解,避免死记硬背。并非所有十进制小数都能用有限位二进制小数精确表示。一个十进制小数能被有限位二进制精确表示,当且仅当它可以表示成形如 k/2nk/2^n 的分数。例如,0.3=3/100.3=3/10,而10不是2的幂(其质因数包含5),因此无法用有限位二进制精确表示。相反,任何有限位二进制小数都对应一个分母为2的幂的分数,因此总能精确地转换为十进制小数。这一特性在浮点数的表示与运算中尤为重要,需特别注意。

2.1.2 定点数的编码表示#

1. 真值和机器数#

在日常生活中,数通常用“+”或“-”号表示正负(正号常省略),如+15、-8。这类带有符号的数称为真值,即机器数所代表的实际数值。在计算机中,数的符号与数值部分一同编码:通常用“0”表示正,“1”表示负。这种将符号数字化的表示形式称为机器数

例如,机器数0,101(逗号仅用于分隔符号位与数值位)表示真值+5。

2. 机器数的定点表示#

根据小数点位置是否固定,计算机中的数值表示分为定点表示浮点表示。定点表示用于表示定点小数定点整数

  1. 定点小数。表示纯小数,约定小数点位于符号位之后、数值部分最高位之前。若数据 X=x0.x1x2xnX=x_0.x_1x_2\ldots x_n (其中 x0x_0 为符号位,x1xnx_1\sim x_n 为数值位,x1x_1 为最高有效位)

  2. 定点整数。表示纯整数,约定小数点位于数值部分最低位之后。若数据 X=x0x1x2.xnX=x_0x_1x_2\cdots .x_n (其中 x0x_0 为符号位,x1xnx_1\sim x_n 为数值位,xnx_n 为最低有效位)

事实上,在机器内部并没有小数点,只是人为约定了小数点的位置。因此,在定点数的编码和运算中,无须区分该数表示的是小数还是整数,而只需关心符号位和数值位即可。

定点数的编码表示法主要有四种:原码、补码、反码和移码。

3. 原码、补码、反码、移码#

(1) 原码表示法#

用机器数的最高位表示数的符号,其余各位表示数的绝对值。原码的定义如下。

[x]={0,x0x<2n2nx=2n+x2n<x0(字长为 n+1)[x]_{\text{原}} = \begin{cases} 0,x & 0 \le x < 2^n \\ 2^n-x=2^n+|x| & -2^n < x \le 0 \end{cases} \quad (\text{字长为 } n+1)

例如,若字长为8位,x1=+1110x_1=+1110x2=1110x_2=-1110,则其原码表示分别为 [x1]=0,0001110[x_1]_{\text{原}}=0,0001110[x2]=27+1110=1,0001110[x_2]_{\text{原}}=2^7+1110=1,0001110

对于 n+1n+1 位原码整数,其表示范围(2n1)x2n1-(2^n-1) \le x \le 2^n-1(关于原点对称)。

NOTE

零的原码表示有正零和负零两种形式,即 [+0]=0,0000000[+0]_{\text{原}}=0,0000000[0]=1,0000000[-0]_{\text{原}}=1,0000000

原码表示的优点: - 与真值的对应关系简单、直观,转换简便; - 用原码实现乘除运算比较简便。 缺点: - 零的表示不唯一,存在 ±0\pm 0 两种编码; - 用原码实现加减运算比较复杂。

(2) 补码表示法#

补码表示法的加法和减法运算均可通过加法器统一实现。正数的补码与原码相同,负数的补码等于反码的末位+1。补码的定义如下。

[x]={0,x0x<2n2n+1+x=2n+1x2nx<0(mod2n+1)[x]_{\text{补}} = \begin{cases} 0,x & 0 \le x < 2^n \\ 2^{n+1}+x=2^{n+1}-|x| & -2^n \le x < 0 \end{cases} \pmod{2^{n+1}}

对于 n+1n+1 位补码整数,其表示范围2nx2n1-2^n \le x \le 2^n-1(比原码多表示一个负数,即 2n-2^n)。

  • 几个特殊值的补码(n+1n+1位):
  1. [+0]=[0]=0,000[+0]_{\text{补}}=[-0]_{\text{补}}=0,00\ldots0(全0),零的补码表示是唯一的
  2. [1]=2n+11=1,111[-1]_{\text{补}}=2^{n+1}-1=1,11\ldots1 (全1)。
  3. 最大正整数[2n1]=0,111[2^n-1]_{\text{补}}=0,11\ldots1(符号位为0,数值位全1)。
  4. 最小负整数[2n]=1,000[-2^n]_{\text{补}}=1,00\ldots0(符号位为1,数值位全0)。
  • 模运算(了解)

在模运算中,一个数与它除以“模”后得到的余数是等价的。如 A,B,MA,B,M 满足 A=B+K×MA=B+K\times M(KK为整数),记为 AB(modM)A\equiv B \pmod M,即 AABB 各除以 MM 后的余数相同。在补码运算中,[A][B]=[A]+M[B][A]_{\text{补}}-[B]_{\text{补}}=[A]_{\text{补}}+M-[B]_{\text{补}},而 M[B]=[B]M-[B]_{\text{补}}=[-B]_{\text{补}},因此补码能够借助加法运算实现减法运算。

  • 补码与真值之间的转换

真值转换为补码:对于正数,与原码的方式一样。对于负数,符号位取1,其余各位由其绝对值“按位取反,末位加1”得到。补码转换为真值:若符号位为0,则直接读作正数。若符号位为1,则真值为负数,其绝对值由补码数值部分“按位取反,末位加1”得到。

  • 变形补码

为便于溢出检测,可采用双符号位的补码表示(又称变形补码),双符号位00表示正数,11表示负数。若总位数为 n+2n+2 (高2位为符号位,其余为数值位),则变形补码定义为

[x]变补={00,x0x<2n2n+2+x=2n+2x2nx<0(mod2n+2)[x]_{\text{变补}} = \begin{cases} 00,x & 0 \le x < 2^n \\ 2^{n+2}+x=2^{n+2}-|x| & -2^n \le x < 0 \end{cases} \pmod{2^{n+2}}

在双符号位中,左符表示真正的符号位,右符用于判断“溢出”。

(3) 反码表示法(了解即可)#

反码可视为从原码转换为补码的中间表示形式。

正数的反码与其原码相同。负数的反码由其原码的数值部分按位取反(末位不加1)得到。反码表示存在明显不足:

  • 零的表示不唯一(存在 ±0\pm 0 两种编码);
  • 表示范围与相同字长的原码相同,比补码少一个最小负数 (2n)(-2^n)

因此,反码在计算机中极少使用。

(4) 移码表示法#

移码主要用于表示浮点数的阶码,且用于表示整数。其核心思想是将真值 xx 加上一个固定偏置值,实现数轴整体右移。设字长为 n+1n+1 位,偏置值通常取 2n2^n,则移码定义为

[x]=2n+x(2nx<2n)[x]_{\text{移}} = 2^n + x \quad (-2^n \le x < 2^n)
NOTE

在IEEE754标准的浮点数中,kk 位阶码的偏置值为 2k112^{k-1}-1,如8位阶码的偏置值为127。

移码(设字长为 n+1n+1,偏置值为 2n2^n)的主要特点如下:

  • 零的表示唯一,[+0]=2n+0=[0]=2n0=100n0[+0]_{\text{移}}=2^n+0=[-0]_{\text{移}}=2^n-0=1\underbrace{0\ldots0}_{n\text{个}0}
  • 在相同字长下,移码与补码仅符号位相反(将补码的最高位取反即得移码)。
  • 移码全0时,对应真值的最小值 2n-2^n;移码全1时,对应真值的最大值 2n12^n-1
  • 移码保持真值的大小顺序:移码值越大,对应真值越大,便于阶码比较。

四种编码表示的总结如下:

  • 正数的原码、反码、补码相同;移码则不同。
  • 原码与反码在数轴上关于原点对称,二者都存在+0与-0。
  • 补码与移码的表示不对称,零的表示唯一,且比原码和反码多表示一个负数 (2n)(-2^n)
  • 原码可直观的比较大小(因数值部分即绝对值),而负数的补码和反码不能像原码那样直观判断。不过,在同为负数的前提下,补码或反码的数值部分越大,其真值也越大

2.1.3 整数的表示#

1. 无符号整数的表示#

当所有二进制位均用于表示数值(无符号位)时,该编码称为无符号整数,简称无符号数。此时,数值隐含为非负整数。由于无须保留符号位,在相同字长下,无符号整数能表示的最大值大于有符号整数。无符号整数适用于仅涉及非负整数且结果不会产生负值的场景。例如,可用无符号整数进行地址运算,或用它来表示指针。

例如,8位无符号整数的最小值为00000000(0),最大值为11111111(281=2552^8-1=255),表示范围为 02550\sim 255;而8位有符号整数(补码表示)的最小值为10000000(27=128-2^7=-128),最大值为01111111(271=1272^7-1=127),表示范围为 128127-128\sim 127

2. 有符号整数的表示#

有符号整数通过在数值位前增设一位符号位(0表示正,1表示负)来表示正负。虽然原码、反码和补码均可用于表示有符号整数,但现代计算机统一采用补码,因其具有以下优势:

  • 零的表示唯一(无+0与-0之分)。
  • 符号位可与数值位一同参与运算,使加减法统一为加法操作。
  • 表示范围更大,比原码和反码多表示一个最小负数。

因此,nn 位有符号整数(补码)的表示范围为 2n12n11-2^{n-1}\sim 2^{n-1}-1

2.1.4 C语言中的整数类型及类型转换#

1. C语言中的整型数据类型#

C语言提供了多种整型类型,其具体长度依赖于编译器和目标平台。常见情况如下:

  • 短整型:short(或short int),通常为16位。
  • 整型:int,通常为32位。
  • 长整型:long(或long int),在32位系统中为32位,在64位系统中通常为64位。

在上述类型前添加 unsigned 关键字,可定义对应的无符号类型(如 unsigned intunsigned short 等)。若未显式指定 signedunsigned,则默认为有符号类型。

字符型(char,通常为8位)是一种特殊的整型,通常可按无符号整数解释。

在现代系统中,所有有符号整型均以补码形式存储。无符号整型则将全部位用于表示非负数值。因此,在相同位宽下,两者的取值范围不同。

2. 整型数据的类型转换#

定点数在类型转换过程中,若涉及字长变化,则会触发两种基本操作:位截断位扩展

  1. 位截断:当长类型转换为短类型时,系统直接丢弃高位,仅保留低位部分。由于目标类型的表示范围较小,截断可能导致数值发生变化,具有较强的隐蔽性。
  2. 位扩展:当短类型转换为长类型时,系统通过填充高位来保持数值语义不变。具体扩展的方式取决于源数据的符号性
  • 零扩展:用于无符号数,在高位补0。
  • 符号扩展:用于补码表示的有符号数,高位重复填充符号位。

C语言支持通过强制类型转换实现不同类型间的转换,其语法为 TYPE b = (TYPE)a;,转换结果是一个 TYPE 类型的值。根据源类型与目标类型的字长和符号性,可分为三种情形。

(1) 长类型转换为短类型:位截断#

转换规则:保留低位,丢弃高位。需要注意的是,此类转换不会触发任何异常或错误报告,具有很强的隐蔽性。

(2) 相同字长的转换:仅改变解释方式#

转换规则:二进制位模式保持不变,仅重新解释其含义。所有二进制位均保持不变

相同字长的整型类型转换不改变位模式,仅改变对这些位的解释方式

(3) 短类型转换为长类型:位扩展#

转换规则:若源数据为有符号数,则执行符号扩展;若源数据为无符号数,则执行零扩展。