计算机中有符号数的表示

二进制数制

所谓数制是指计数的方法。

十进制

人两手加起来共10根手指,故日常计数和做算术都使用十进制。大家熟悉并使用了一千多年的十进制起源于印度,在12世纪被阿拉伯数学家改进,并在13世纪被意大利数学家Leonardo Pisano(Fibonacci)带到西方。1、2、3的罗马计数法是Ⅰ、Ⅱ、Ⅲ,Ⅰ+Ⅱ=Ⅲ 直观展示了加法运算的含义。

数据无论使用哪种进位制,都涉及两个基本要素:基数(radix)与各数位的“位权”(weight)。
十进制数有两个特点:

  1. 用0、1、2、3、…、9这10个基本符号表示;基本数字符号(数码)的个数叫基数
  2. 遵循“逢十进一”原则,每位计满十时向高位进一。

一般地,任意一个十进制数 N 都可以表示为 ∑ i = − m n − 1 K i ∗ 1 0 i \sum_{i=-m}^{n-1}K_i\ast10^i i=mn1Ki10i

N = K n − 1 ∗ 1 0 n − 1 + K n − 2 ∗ 1 0 n − 2 + ⋯ + K 1 ∗ 1 0 1 + K 0 ∗ 1 0 0 + K − 1 ∗ 1 0 − 1 + K − 2 ∗ 1 0 − 2 + ⋯ + K − m ∗ 1 0 − m N = K_{n-1}\ast10^{n-1} + K_{n-2}\ast10^{n-2} + \cdots + K_1\ast10^1 + K_0\ast10^0 + K_{-1}\ast10^{-1} + K_{-2}\ast10^{-2} + \cdots + K_{-m}\ast10^{-m} N=Kn110n1+Kn210n2++K1101+K0100+K1101+K2102++Km10m

抛开小数部分,整数按权的展开式为:

N = ∑ i = 0 n − 1 K i ∗ 1 0 i = K n − 1 ∗ 1 0 n − 1 + K n − 2 ∗ 1 0 n − 2 + ⋯ + K 1 ∗ 1 0 1 + K 0 ∗ 1 0 0 N = \sum_{i=0}^{n-1}K_i\ast10^i = K_{n-1}\ast10^{n-1} + K_{n-2}\ast10^{n-2} + \cdots + K_1\ast10^1 + K_0\ast10^0 N=i=0n1Ki10i=Kn110n1+Kn210n2++K1101+K0100

一个数字符号在不同位时,代表的数值不同。在上述表达式中,数位 K i K_i Ki 的权为 1 0 i 10^i 10i(以基数 10 10 10为底,序号 i i i为指数),数字符号乘以其位权为这个数字符号所表示的真实数值( K i ∗ 1 0 i K_i\ast10^i Ki10i)。

二进制

字节存储单元及struct内存分配 中,我们介绍了二进制和以及字节存储单元。现代计算机存储和处理的信息以二值信号表示。这些微不足道的二进制数字,或者称为位(bit),形成了数字革命的基础。

对于有10根手指的人来来说,使用十进制表示法是很自然的事情,但是当构造存储和处理信息的机器时,二进制工作得更好。在计算机内部,二进制总是存放在由具有两种相反状态的存储元件构成的寄存器或存储单元中,即二进制数码0和1是由存储元件的两种相反状态来表示的。这使得二值信号很容易地被表示、存储和传输。

二值信号可以表示为导线上的高电压或低电压、晶体管的导通或截止、电子自旋的两个方向,或者顺时针或逆时针的磁场。指令集及流水线 中提到,在上个世纪的打孔编程时代,纸带上的每个孔代表一位(bit),穿孔(presence)表示1,未穿孔(absence)表示0,这些孔序列被扫描识别为机器指令的二进制位串。

十进制数按权的展开式可以推广到任意进位计数制。二进制中只有0和1两个字符,基数为2,满足“逢二进一”。权用 2 i 2^i 2i 表示,二进制的按权展开式为 N = ∑ i = 0 n − 1 K i ∗ 2 i N = \sum_{i=0}^{n-1}K_i\ast2^i N=i=0n1Ki2i

二进制与其他数制相比,有以下显著特点:

  1. 数制简单,容易基于元器件的电子特性实现数字逻辑电路。
  2. 由于二进制只有两种状态,因此抗干扰性强,可靠性、稳定性高。
  3. 可以基于布尔逻辑代数进行分析和综合,运算规则相对简单易实现。

基数为2的好处在于基本算术运算表很短,对比一下十进制和二进制的加法和乘法表,长短相形一目了然。

位模式

2个比特可以组合出4( 2 2 2^2 22)种状态,可表示无符号数值范围[0,3];32个比特可以组合出4294967296( 2 32 2^{32} 232)种状态,可表示无符号数值范围[0,4294967295];……。

由于一个位只能表示二元数值,所以单独一位的用处不大。当把位组合在一起,再加上某种解释(interpretation),即赋予不同的可能位模式以含意。通常将固定位数的位串作为一个基本存储单位,这样就可以存储范围较大的值。在有限范围内的可计量数值几乎都可以用二进制数码位串组合表示,计算机的内存由数以亿万计的比特位存储单元(晶体管)组成。

大多数计算机使用8位的块,或者字节(byte),作为最小的可寻址的内存单位,而不是访问内存中单独的位(bit)。机器级程序将内存视作一个非常大的字节数组,内存的每个字节都由一个唯一的数字来标识,称为它的地址。

每个程序对象可以简单地视为一个字节块,程序本身就是一个字节序列(机器指令序列)。

基本数据类型

C++ Variable Types 中,我们总结了C/C++中的基本数据类型。C/C++语言支持多种整形数据类型——表示有限范围的整数。每种类型都用关键字来指定大小,这些关键字包括 char、short、long,参考 gnu libc Integers。C/C++语言都支持有符号(默认)和无符号数。

  1. 长短修饰符 shortlong 用于修饰整形:默认为 long 长整形,短整形需显式指定 short。
  2. 符号修饰符 signedunsigned 用于修饰字符型和整形:缺省为 signed 有符号类型,无符号需显式指定 unsigned 修饰。
  3. 当用 signed / unsignedshort / long 来修饰 int 整形时,int 可省略。

以下是字符型、短整型、整型有无符号的区分表示:

  • 有符号字符型:char/signed char;无符号字符型:unsigned char
  • 有符号短整型:short [int] /signed short [int];无符号短整型:unsigned short [int]
  • 有符号整型:int /signed [int];无符号整型:unsigned [int]

无符号数的编码

无符号(unsigned)编码基于传统的二进制表示法,表示大于或等于0的非负数。

假设有一个整数数据类型有 n n n 位。我们可以将位向量写成 x ⃗ \vec{x} x 表示整个向量,或者写成 [ x n − 1 , x n − 2 , ⋯   , x 0 ] [x_{n-1}, x_{n-2}, \cdots, x_0] [xn1,xn2,,x0],表示向量中的每一位。把 x ⃗ \vec{x} x 看成一个二进制表示的数,就获得了 x ⃗ \vec{x} x 的无符号表示。在这个二进制编码中,每个位 x i x_i xi 都取值0或1,后一种取值意味着数值 2 i 2^i 2i 应为数字值的一部分。我们用一个函数 B 2 U n B2U_n B2Un(Binary to Unsigned的缩写,长度为 n n n)来表示。

对位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2}, \dotsc, x_0] x =[xn1,xn2,,x0]

B 2 U n ( x ⃗ ) = ˙ ∑ i = 0 n − 1 x i ∗ 2 i B2U_n(\vec{x}) \dot=\sum_{i=0}^{n-1}x_i\ast2^i B2Un(x )=˙i=0n1xi2i

在这个等式中,等号“ = ˙ \dot= =˙”表示左边被定义为等于右边。函数 B 2 U n B2U_n B2Un 将一个长度为 n n n 的0、1串映射到非负整数。

n n n 位所能表示的最小值用位向量 [ 00 ⋯ 0 ] [00\cdots0] [000](全零)表示,也就是整数值0,而最大值是用位向量 [ 11 ⋯ 1 ] [11\cdots1] [111](全1),也就是整数值 U M a x n = ˙ ∑ i = 0 n − 1 x i ∗ 2 i = 2 n − 1 UMax_n\dot=\sum_{i=0}^{n-1}x_i\ast2^i=2^n-1 UMaxn=˙i=0n1xi2i=2n1。即 n n n位二进制位串所能表示的数值范围是 [ 0 , 2 n − 1 ] [0, 2^n-1] [0,2n1]

在主流64位LP64架构实现中,unsigned char、unsigned short [int]、unsigned [int]、unsigned long [int](或 long unsigned [int])分别占1、2、4、8个字节(8、16、32、64位)。无符号字符(unsigned char)占用1个字节(8位),所能表示的数值范围是 [ 0 , 2 8 − 1 ] = [ 0 , 255 ] [0, 2^8-1] = [0, 255] [0,281]=[0,255],共 2 8 2^8 28=256 个数。

有符号数的编码

对于很多应用,我们还希望表示负数值。在计算机中,如何表示符号呢?
在计算机中,对于数的符号(正号+和负号-)也只能用0和1这两位数字表示。通常用一个数的最高位作为符号位,最高位为0表示符号位为正;最高位为1表示符号位为负。这样,数的符号标识也“数码化”了。即带符号数的数值和符号统一用二进制数码形式来表示。

在将数的符号用数码(0或1)表示后,数值部分究竟是保留原来的形式,还是按一定的规则做某些变化,这要取决于运算方法的需要,从而有四种常见的机器数形式:原码反码补码移码

为了区别原来的数与它在机器中的表示形式,将一个数(连同符号)在机器中加以数码化后的形式,称为机器数或机器码,而把机器数所代表的实际数值称为真值

原码(Sign-Magnitude)

原码表示法比较直观,其数值部分保留其真值(的绝对值)。
原码
例如,正数89的二进制表示为 + 1011001 +1011001 +1011001,正号 + 直接用0代替,其机器原码表示为:
01011001
负数-89的二进制表示为 − 1011001 -1011001 1011001(数值位宽为7),负号 - 直接用1代替,其机器原码表示为:
11011001
若符号真值为X,最终机器原码的位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2} ,\dotsc, x_0] x =[xn1,xn2,,x0] 宽度为 n n n,除最高符号位 x n − 1 x_{n-1} xn1 外的数值位宽为 n − 1 n-1 n1,则可将原码表示规则用表达式形式定义如下:

[ X ] 原 = { X , X = + 0 ∣ ∣ 0 < X < 2 n − 1 2 n − 1 − X = 2 n − 1 + ∣ X ∣ , X = − 0 ∣ ∣ − 2 n − 1 < X < 0 % 原码 [X]_原= \begin{cases} X,& X=+0 \quad || \quad 0 \lt X \lt 2^{n-1} \\ 2^{n-1}-X = 2^{n-1}+\lvert X \rvert,& X=-0 \quad || -2^{n-1} \lt X \lt 0 \\ \end{cases} [X]={X2n1X=2n1+XX=+0∣∣0<X<2n1X=0∣∣2n1<X<0

  • 8位二进制原码表示的数值范围为 11111111-10000000,00000000-01111111,即 -127 - -0,+0 - +127。其中,“0”有-0和+0之分, [ − 0 ] 原 = 10000000 [-0]_原=1 0000000 [0]=10000000 [ + 0 ] 原 = 00000000 [+0]_原=0 0000000 [+0]=00000000
  • [ − 89 ] 原 = 2 8 − 1 − ( − 89 ) = 2 7 + 89 = 0 b 10000000 + 0 b 1011001 = 0 b 11011001 [-89]_原=2^{8-1}-(-89) = 2^7+89 = 0b10000000+0b1011001 = 0b11011001 [89]=281(89)=27+89=0b10000000+0b1011001=0b11011001

原码表示法的优点是比较直观、简单易懂,后面在浮点数中有使用到原码编码。
原码的符号位不是数值的一部分,不能直接参与运算,导致加法运算复杂。
为了解决这些矛盾,人们引入了反码和补码。

反码(Ones’ Complement)

对于正数而言,其反码形式与其原码相同:最高位为符号位,用0表示正数,其余位为数值位不变。对于负数而言,其反码表示为:最高位符号位为1,其余数值位在原码的基础上按位取反。反码在机器中的表示形式如下:
反码
-89的二进制表示为 − 1011001 -1011001 1011001,其原码表示为 11011001 1 1011001 11011001,则其反码表示为 10100110 1 0100110 10100110
在这里插入图片描述
若符号真值为X,最终机器反码的位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2} ,\dotsc, x_0] x =[xn1,xn2,,x0] 宽度为 n n n,除最高符号位 x n − 1 x_{n-1} xn1 外的数值位宽为 n − 1 n-1 n1,则可将反码表示规则用表达式形式定义如下:

[ X ] 反 = { X , X = + 0 ∣ ∣ 0 < X < 2 n − 1 ( 2 n − 1 ) + X = ( 2 n − 1 ) − ∣ X ∣ , X = − 0 ∣ ∣ − 2 n − 1 < X < 0 % 反码 [X]_反= \begin{cases} X,& X=+0 \quad || \quad 0 \lt X \lt 2^{n-1} \\ (2^n-1) + X = (2^n-1)- \lvert X \rvert,& X=-0 \quad || -2^{n-1} \lt X \lt 0 \end{cases} [X]={X(2n1)+X=(2n1)XX=+0∣∣0<X<2n1X=0∣∣2n1<X<0

  • 当 X<0 或 X=-0 时,按照无符号数解析位向量, ∣ X ∣ + [ X ] 反 → = 2 n − 1 \lvert X \rvert + \overrightarrow{[X]_反}=2^n-1 X+[X] =2n1(n位全1)。例如当n=8时, ∣ X ∣ + [ X ] 反 → = 0 b 11111111 = 255 \lvert X \rvert + \overrightarrow{[X]_反}=0b11111111=255 X+[X] =0b11111111=255 [ X ] 反 → \overrightarrow{[X]_反} [X] 相当于 ∣ X ∣ \lvert X \rvert X 的按位取反(bitwise inversion)= ~ ∣ X ∣ \lvert X \rvert X
  • 8位二进制反码表示的数值范围为 10000000-11111111,00000000-01111111(负数数值部分求反复原:11111111-10000000,00000000-01111111),即 -127 - -0,+0 - +127。其中,“0”有-0和+0之分, [ − 0 ] 反 = 11111111 [-0]_反=1 1111111 [0]=11111111 [ + 0 ] 反 = 00000000 [+0]_反=0 0000000 [+0]=00000000

求-89的8位反码时,无需按照“原码→反码”这个步骤,基于以上表达式即可计算出其反码的无符号真值,其对应的二进制位向量即为-89的反码。

  • [ − 89 ] 反 = ( 2 8 − 1 ) + ( − 89 ) = ( 2 8 − 1 ) − ( 89 ) = 166 = 0 b 10100110 [-89]_反=(2^8-1)+(-89) = (2^8-1)-(89) = 166 = 0b10100110 [89]=(281)+(89)=(281)(89)=166=0b10100110
  • [ − 89 ] 反 [-89]_反 [89]=~(-89)=166= 0 b 10100110 0b10100110 0b10100110

虽然过去生产过基于反码表示的机器,但是几乎所有的现代机器都使用补码形式表示有符号整数。
现在通常已不再单独使用反码,而主要是作为求补码的一个中间步骤来使用。

补码(Two’s Complement)

在计算机中,最常见的有符号(整)数的表示方式是补码。采用补码运算可以将减法变成补码加法运算,在微处理器中只需加法的电路就可以实现加法、减法运算。

概念导读

为了理解补码的概念,我们先来看看圆周运动的例子。

在现实生活中,一个圆周的可视角度度量为0-2π(或0°-360°),以原点为起点的射线OA逆时针旋转一圈的弧度为2π。

圆周运动具有周期性,即具有“周而复始”的变化规律。假设OA的初始弧度为α,终边OA每绕原点旋转一周,α增加2π弧度(旋转k周后,弧度变成α+k·2π),但OA位置不变。由三角函数的定义可知,终边相同的角的同一三角函数的值相等。

sin ⁡ ( α + k ⋅ 2 π ) = sin ⁡ α cos ⁡ ( α + k ⋅ 2 π ) = cos ⁡ α tan ⁡ ( α + k ⋅ 2 π ) = tan ⁡ α \begin{gather*} \sin(\alpha+k·2\pi) = \sin\alpha \\ \cos(\alpha+k·2\pi) = \cos\alpha \\ \tan(\alpha+k·2\pi) = \tan\alpha \end{gather*} sin(α+k2π)=sinαcos(α+k2π)=cosαtan(α+k2π)=tanα

由上面的公式可知,可以把求任意角的三角函数值,转化为求0-2π(或0°-360°)角的三角函数值。射线OA逆时针旋转π和顺时针旋转π(-π)的终边是相同的,逆时针旋转5π/3和顺时针旋转π/3(-π/3)的终边是相同的。以下将负弧度的正弦计算转化到0-2π区间换算:

sin ⁡ ( − π + 2 π ) = sin ⁡ π sin ⁡ ( − π 3 + 2 π ) = sin ⁡ 5 π 3 \begin{gather*} \sin(-\pi+2\pi) = \sin\pi \\ \sin(-\frac{\pi}{3}+2\pi) = \sin\frac{5\pi}{3} \end{gather*} sin(π+2π)=sinπsin(3π+2π)=sin35π


我们再来进一步看看日常生活中校正时钟的例子。

假定时钟停在7点,而正确时间是5点,要拨准时钟可以有两种不同的拨法:倒拨2个格或顺拨10个格。

想象一下,龟兔在7点钟刻度背向而行,假设兔子的速度是乌龟的5倍,兔子顺时针跑10格,乌龟逆时针跑2格,它们将在5点刻度处迎面相遇。
在这里插入图片描述
由于钟面的容量有限,其表盘刻度实际上是十二进制,12h以后又从0开始计数。倒拨2个格,即7-2=5(做减法);顺拨10个格,即7+10=12+5(做加法)。而钟面上12=0,故12+5回归到刻度5。这就表明,在舍掉进位的情况下,“从7减去2”和“往7加上10”所得的结果是一样的。在十二进制下,12+5丢失进位12,此处12是溢出量,又称为(mod)。而-2和10的绝对值之和恰好等于模数12,我们把10称为-2对于模数12的补数。在圆周运动中,每转动一周的 2 π 2\pi 2π 弧度可视为溢出量(模), 5 π 3 \frac{5\pi}{3} 35π − π 3 -\frac{\pi}{3} 3π 对于模数 2 π 2\pi 2π 的补数。

10 ≡ − 2 m o d    12 5 π 3 ≡ − π 3 m o d    2 π \begin{align*} 10 & \equiv -2 & \mod 12 \\ \frac{5\pi}{3} & \equiv -\frac{\pi}{3} & \mod 2\pi \end{align*} 1035π23πmod12mod2π

在电脑和手机的日期和时间偏好设置中,通常可以设置显示24小时,因为地球自转一圈是一天(接近24h)。24h制的01点和13点(下午1点)都对应12h制钟表盘上的1点位: 1 ≡ 13 m o d    12 1 \equiv 13 \mod 12 113mod12。“天天向上”、“日复一日”中的“天”和“日”即为溢出量,每过24h(模)又将开启崭新的一天。假设现在是今天0点,则32h后就是明天上午8点: 8 ≡ 32 m o d    24 8 \equiv 32 \mod 24 832mod24

计算机中的运算受一定字长的限制,它的运算部件、寄存器和存储单元都有一定的位数,因而在运算过程中也会产生溢出量,所产生的溢出量实际上就是模。可见,计算机的运算也是一种有模运算

编码格式

对于正数而言,其补码形式与其原码、反码相同:最高位为符号位,用0表示正数,其余位为数值位不变。对于负数而言,其补码表示为:最高位符号位为1,其余数值位在原码的基础上按位取反并加1(反码+1)。补码在机器中的表示形式如下:
补码
-89的二进制表示为 − 1011001 -1011001 1011001,其原码表示为 11011001 1 1011001 11011001,其反码表示为 10100110 1 0100110 10100110,则其补码表示为 10100111 1 0100111 10100111
补码
若符号真值为X,最终机器补码的位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2} ,\dotsc, x_0] x =[xn1,xn2,,x0] 宽度为 n n n,除最高符号位 x n − 1 x_{n-1} xn1 外的数值位宽为 n − 1 n-1 n1,则可将反码表示规则用表达式形式定义如下:

[ X ] 补 = { X , 0 ≤ X < 2 n − 1 2 n + X = 2 n − ∣ X ∣ , − 2 n − 1 ≤ X < 0 ( m o d    2 n ) % 补码 [X]_补= \begin{cases} X, & 0 \le X \lt 2^{n-1} \\ 2^n+X = 2^n- \lvert X \rvert, & -2^{n-1} \le X \lt 0 \quad (\mod 2^n) \end{cases} [X]={X,2n+X=2nX,0X<2n12n1X<0(mod2n)

  • 当 X<0 时,按照无符号数解析位向量, ∣ X ∣ + [ X ] 补 → = 2 n \lvert X \rvert+\overrightarrow{[X]_补}=2^n X+[X] =2n(相对反码加1)。例如当n=8时, ∣ X ∣ + [ X ] 补 → = 2 8 = 256 \lvert X \rvert+\overrightarrow{[X]_补}=2^8=256 X+[X] =28=256。8位全1(0b11111111)时,能表示最大无符号数 2 8 − 1 2^8-1 281 或最大有符号数( − 1 -1 1),再加1则会超出8位所能表示的最大值,变成9位二进制0b100000000,高位截断溢出为0b00000000,即超过256从零开始计数( 0 ≡ 256 m o d    256 0 \equiv 256 \mod 256 0256mod256)。
  • 8位二进制补码表示的数值范围为 10000000-11111111,00000000-01111111,即 -128 - -1,+0 - +127。其中,无-0和+0之分,保证了0的唯一性。另外,取值范围为连贯区间 [-128, 127],包含128个负数、128个非负数。负数、非负数各占一半,负数比正数多一个。

设想一个有256个刻度的大笨钟,当采用补码重新编码刻度后,表盘的256个刻度被重新编码,划分成右边顺时针半盘[0,127]和左边逆时针半盘[-128,-1]。
在这里插入图片描述


-89的二进制补码表示为 10100111 1 0100111 10100111,按照无符号数解析位向量的值为167,满足:

∣ − 89 ∣ + 167 = 256    ⟹    167 ≡ − 89 m o d    256 \lvert -89 \rvert + 167 = 256 \implies 167 \equiv -89 \mod 256 89+167=25616789mod256

求-89的8位补码时,无需按照“原码→反码→+1”这个步骤,可基于以上表达式计算其补数:

  • [ − 89 ] 补 = 2 8 + ( − 89 ) = 2 8 − 89 = 167    ⟹    − 89 m o d    256 = 256 + ( − 89 ) ≡ 167 [-89]_补=2^8+(-89) = 2^8-89 = 167 \implies -89 \mod 256 = 256+(-89) \equiv 167 [89]=28+(89)=2889=16789mod256=256+(89)167

负数-89对应的无符号补数167的二进制位向量 0b10100111,即为其补码。

按权展开

-89的二进制表示为 − 1011001 -1011001 1011001,其原码表示为 11011001 1 1011001 11011001,其反码表示为 10100110 1 0100110 10100110,其补码表示为 10100111 1 0100111 10100111

∣ − 89 ∣ \lvert -89 \rvert 89 的二进制位向量为 01011001 01011001 01011001

  • 不考虑最高符号位,根据反码的定义,反码数值部分位向量= ( 2 n − 1 − 1 ) − ∣ X ∣ = n = 8 127 − ∣ − 89 ∣ = 0 b 01111111 − 0 b 01011001 = 0 b 00100110 (2^{n-1}-1)-\lvert X \rvert \xlongequal{n=8} 127-\lvert -89 \rvert=0b01111111-0b01011001=0b00100110 (2n11)Xn=8 12789=0b011111110b01011001=0b00100110
  • 不考虑最高符号位,补码数值部分位向量= ( 2 n − 1 − 1 ) − ∣ X ∣ + 1 = 2 n − 1 − ∣ X ∣ = n = 8 127 − ∣ − 89 ∣ + 1 = 0 b 01111111 − 0 b 01011001 + 1 = 0 b 00100111 (2^{n-1}-1)-\lvert X \rvert+1 = 2^{n-1}-\lvert X \rvert \xlongequal{n=8} 127-\lvert -89 \rvert+1=0b01111111-0b01011001+1=0b00100111 (2n11)X+1=2n1Xn=8 12789+1=0b011111110b01011001+1=0b00100111

补码高位符号位占1位,其余数值部分占 n-1 位。当将最高符号位解释为负权( − 2 n − 1 -2^{n-1} 2n1)时,整体位向量的真值即为原始负值:

− 2 n − 1 ⏟ 1 + 2 n − 1 − ∣ X ∣ ⏟ n − 1 = − ∣ X ∣ \underbrace{-2^{n-1}}_{1} + \underbrace{2^{n-1}-\lvert X \rvert}_{n-1} = -\lvert X \rvert 1 2n1+n1 2n1X=X

我们用一个函数 B 2 T n B2T_n B2Tn(Binary to Two’s Complement 的缩写,长度为 n n n)来表示位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2} , \dotsc, x_0] x =[xn1,xn2,,x0] 到补码的编码映射:

B 2 T n ( x ⃗ ) = ˙ − x n − 1 ∗ 2 n − 1 + ∑ i = 0 n − 2 x i ∗ 2 i B2T_n(\vec{x}) \dot=-x_{n-1}\ast2^{n-1} + \sum_{i=0}^{n-2}x_i\ast2^i B2Tn(x )=˙xn12n1+i=0n2xi2i

最高有效位 x n − 1 x_{n-1} xn1 称为符号位,它的“权重”为 − 2 n − 1 -2^{n-1} 2n1,是无符号表示中权重的负数。符号位被设置为1时,表示值为负,而当设置为0时,值为非负。

∣ − 89 ∣ \lvert -89 \rvert 89 的二进制位向量为 01011001 01011001 01011001,-89的二进制补码表示为 10100111 1 0100111 10100111,可基于 B 2 T n B2T_n B2Tn 函数按权展开复原补码的真值:

B 2 T 8 ( [ 01011001 ] ) = − 0 ∗ 2 7 + 1 ∗ 2 6 + 1 ∗ 2 4 + 1 ∗ 2 3 + 1 ∗ 2 0 = − 0 + 64 + 16 + 8 + 1 = 89 B 2 T 8 ( [ 10100111 ] ) = − 1 ∗ 2 7 + 1 ∗ 2 5 + 1 ∗ 2 2 + 1 ∗ 2 1 + 1 ∗ 2 0 = − 128 + 32 + 4 + 2 + 1 = − 89 \begin{aligned} & B2T_8([0 1011001]) = -0\ast2^7+1\ast2^6+1\ast2^4+1\ast2^3+1\ast2^0=-0+64+16+8+1=89 \\ & B2T_8([1 0100111]) = -1\ast2^7+1\ast2^5+1\ast2^2+1\ast2^1+1\ast2^0=-128+32+4+2+1=-89 \end{aligned} B2T8([01011001])=027+126+124+123+120=0+64+16+8+1=89B2T8([10100111])=127+125+122+121+120=128+32+4+2+1=89

让我们基于 B 2 T n B2T_n B2Tn 展开式,重新推导一下 n n n 位补码所能表示的取值范围。

  • 最小值是位向量 [ 10 ⋯ 0 ] [10\cdots0] [100],只设置负权,其他正权位清零,此种情形负得最多,其整数值为 T M i n n = ˙ − 2 n − 1 TMin_{n}\dot=-2^{n-1} TMinn=˙2n1
  • 当设置了负权时,设置其他所有正权位,位向量为 [ 11 ⋯ 1 ] [11\cdots1] [111]。此种情形正权打满(负得最少),其整数值为 − 2 n − 1 + ∑ i = 0 n − 2 x i ∗ 2 i = − 2 n − 1 + ( 2 n − 1 − 1 ) = − 1 -2^{n-1}+\sum_{i=0}^{n-2}x_i\ast2^i=-2^{n-1}+(2^{n-1}-1)=-1 2n1+i=0n2xi2i=2n1+(2n11)=1,即最大负整数。
  • 最大值是位向量 [ 01 ⋯ 1 ] [01\cdots1] [011],清除负权(即为非负数),清除其他所有正权位,其值为0;若设置其他所有正权位,其整数值为 T M a x n = ˙ ∑ i = 0 n − 2 x i ∗ 2 i = 2 n − 1 − 1 TMax_{n}\dot=\sum_{i=0}^{n-2}x_i\ast2^i=2^{n-1}-1 TMaxn=˙i=0n2xi2i=2n11

n = 8 n=8 n=8为例,一个字节(byte)的补码编码所能表示数值范围是 [ − 2 8 − 1 , 2 8 − 1 − 1 ] [-2^{8-1}, 2^{8-1}-1] [281,2811],即 [ − 128 , 127 ] [-128, 127] [128,127],包含128个负数(-128 - -1)、128个非负数(0 - 127)。

补码加法

我们在初中时代学习初等代数时,就引入负数这一重要的概念用来表示“相反意义的量”。有了负数的概念后,减法也可以理解为加法:减去一个正数的等效表述是加了一个负数

回想调拨钟表的例子,定义顺时针为正(加法),逆时针为负(减法)。假设初始刻度为x,减量为n(n>0),加量为其补数 (12-n),显然有 x − n ≡ x + ( 12 − n ) m o d    12 x-n \equiv x+(12-n) \mod 12 xnx+(12n)mod12。令 n=2,在 mod12 的钟表盘上,逆时针回拨2格等效于顺时针拨动10格,殊途同归。

计算机的表示法使用有限数量的位对一个数字编码,当结果太大以至不能表示时,某些运算就
会溢出(overflow)。因此,我们说计算机运算也是一种有模运算。当然,在计算机中不是像上述时钟例子那样以12为模,在定点小数的补码表示中是以 2 2 2 为模,在定点整数中则以 2 n 2^n 2n 为模(n=8,16,32,64,…)。

在计算机中,在字宽容量(模数)范围内,负数的补码表示是对数值位(或绝对值)取反+1,“取反”实际上已经暗含了减法,“+1” 则将补码进一步换算成了补数。假设 X<0,其补码位向量的无符号值 [ X ] 补 → = 2 n − ∣ X ∣ \overrightarrow{[X]_补} = 2^n- \lvert X \rvert [X] =2nX,实际上就是 m o d    2 n \mod 2^n mod2n。当 n=8 时, 就是 m o d    256 \mod 256 mod256

类比钟表调拨,减去正数可视作加上负数的补码。这样,在计算机中就不用单独设置减法器,而是基于补码一律按加法运算规则实现减法的等效计算。在这一过程中,操作数和运算结果都统一用补码表示。

另一方面,补码的最高符号位统一按照负权解释,即补码的符号位可视作整体数值的一部分。从这个角度来讲,符号位直接参与运算貌似也是解释得通。

[ X ] 补 + [ Y ] 补 = { X + ( 2 n + Y ) ≡ X + Y m o d    2 n , {   X , Y ∣ X > 0 , Y < 0   } ( 2 n + X ) + ( 2 n + Y ) ≡ X + Y m o d    2 n , {   X , Y ∣ X < 0 , Y < 0   } [X]_补+[Y]_补= \begin{cases} X+(2^n+Y) & \equiv X+Y & \mod 2^n, \set{X,Y|X\gt0, Y\lt0} \\ (2^n+X)+(2^n+Y) & \equiv X+Y & \mod 2^n, \set{X,Y|X\lt0, Y\lt0} \end{cases} [X]+[Y]={X+(2n+Y)(2n+X)+(2n+Y)X+YX+Ymod2n,{X,YX>0,Y<0}mod2n,{X,YX<0,Y<0}

【例1】已知X=+0000111(7),Y=-0010011(-19),求两数的补码之和。

  • [ X ] 补 = 00000111 , [ Y ] 补 = 11101101 [X]_补=0 0000111,[Y]_补=1 1101101 [X]=00000111[Y]=11101101,人工计算 Z=X+Y=7+(-19)=-12(补码为11110100)。

若将 [ Y ] 补 → \overrightarrow{[Y]_补} [Y] 也视作无符号数=237,将计算结果Z也视为无符号数 [ Z ] 补 → \overrightarrow{[Z]_补} [Z] =244,满足 7+237=244,而 244 ≡ − 12 m o d    256 244 \equiv -12 \mod 256 24412mod256

还是借用256大笨钟来阐述,初始在刻度7点处,逆时针回拨19格和顺时针拨动237格,效果都是拨到刻度244处。当采用补码重新编码刻度后,原先的244点映射为左半盘的-12点位。换个角度来说,由于 ∣ − 19 ∣ > ∣ 7 ∣ \lvert -19 \rvert \gt \lvert 7 \rvert 19>7,这里异号相加,负数占主导,也可以想象为初始刻度在左半盘-19点,+7表示顺时针拨动7格,将拨到-12点,仍然在左半盘(没有溢出)。

补码可以视作无符号数直接参与竖式按位加计算,接下来重点看看补码加法的溢出问题、判断及处理。

【例2】已知X=+1000000(64),Y=+1000001(65),求两数的补码之和。

  • 直接对补码列竖式计算如下:

        0   1 ( C f C 0 )       [ X ] 补 0   1000000 ( + 64 的补码 ) + ) [ Y ] 补 0   1000001 ( + 65 的补码 )   1   0000001 129 \begin{array}{c|lcr} \:\:\:\:\: & \: \quad 0\ 1 \qquad\qquad\qquad (C_fC_0)\\ \:\:\:\:\: [X]_补 & \qquad 0\ 1000000 \qquad (+64的补码) \\ +) [Y]_补 & \qquad 0\ 1000001 \qquad (+65的补码) \\ \hline \ & \qquad 1\ 0000001 \qquad 129 \end{array} [X]+)[Y] 0 1(CfC0)0 1000000(+64的补码)0 1000001(+65的补码)1 0000001129

  • 两个正数相加,结果为负数!?补码之和是129,超出了 [0, 127],即产生了溢出现象。此时,数值部分向符号位产生进位 C 0 = 1 C_0=1 C0=1,符号位未向高位产生进位 C f = 0 C_f=0 Cf=0

这里两个正数相加,结果超出了8位补码的非负数表示范围。竖式计算的结果为二进制位向量 10000001,无符号数值是 129,补码表示的真值为-127。

C 0 = 1 C_0=1 C0=1 表示有溢出,符号位从正变成负。

还是借用256大笨钟来阐述,初始在刻度65点处,顺时针拨动64格后将到129点。但是,当采用补码表示时,129点越过右半盘(取值范围为[0,127])的分界线127点两格,对应左半盘(取值范围为[-128,-1])的-127点位:+127左溢一格是-128,再左溢一格是-127。
在这里插入图片描述
用以下C语言代码测试验证,计算结果Z的位向量为0x81(即无符号数129),补码对应的真值= − 127 ≡ 129 m o d    256 -127 \equiv 129 \mod 256 127129mod256

    signed char X = 64;
    signed char Y = 65;
    signed char Z = X+Y;
    printf("Z=0x%hhx, %hhd\n", Z, Z);

【例3】已知X=-1111111(-127),Y=-0000010(-2),要求进行补码的加法运算。

  • 直接对补码列竖式计算如下:

        1   0 ( C f C 0 )       [ X ] 补 1   0000001 ( − 127 的补码: 129 ) + ) [ Y ] 补 1   1111110 ( − 2 的补码: 254 )   0   1111111 127 \begin{array}{c|lcr} \:\:\:\:\: & \: \quad 1\ 0 \qquad\qquad\qquad (C_fC_0)\\ \:\:\:\:\: [X]_补 & \qquad 1\ 0000001 \qquad (-127的补码:129) \\ +) [Y]_补 & \qquad 1\ 1111110 \qquad (-2的补码:254) \\ \hline \ & \qquad 0\ 1111111 \qquad 127 \end{array} [X]+)[Y] 1 0(CfC0)1 0000001(127的补码:129)1 1111110(2的补码:254)0 1111111127

  • 两个负数相加,结果为正数!?预期的结果-129超出了8位补码所能表示的负数范围 [-128, -1],即产生了溢出现象。此时,数值部分向符号位未产生进位 C 0 = 0 C_0=0 C0=0,符号位向高位产生进位 C f = 1 C_f=1 Cf=1

这里两个负数相加,结果超出了8位补码的负数表示范围,溢出到了正数区。竖式计算的结果为二进制位向量 01111111,无符号数值即其补码真值为127。

C f = 1 C_f=1 Cf=1 表示有溢出,符号位从负变成正。

由于 ∣ − 127 ∣ > ∣ − 2 ∣ \lvert -127 \rvert \gt \lvert -2 \rvert 127>2,这里的负负相加,-127占主导。还是借用256大笨钟来阐述,当采用补码重新编码刻度后,想象初始刻度左半盘-127点处,-2表示逆时针拨动2格,越过左半盘分界线-128点一格,对应右半盘(取值范围为[0,127])的127点位:-127右溢一格是-128,再右溢一格是+127。

[ X ] 补 + [ Y ] 补 = ( 2 n + X ) + ( 2 n + Y ) ≡ X + Y m o d    2 n [ − 127 ] 补 8 + [ − 2 ] 补 8 = ( 256 + ( − 127 ) ) + ( 256 + ( − 2 ) ) = 129 + 254 = 383 m o d    256 ≡ 127 [X]_补+[Y]_补=(2^n+X)+(2^n+Y) \equiv X+Y \mod 2^n \\ [-127]_{补8}+[-2]_{补8}=(256+(-127)) + (256+(-2)) = \\ 129+254 = 383 \mod 256 \equiv 127 [X]+[Y]=(2n+X)+(2n+Y)X+Ymod2n[127]8+[2]8=(256+(127))+(256+(2))=129+254=383mod256127

用以下C语言代码测试验证,计算结果Z的位向量为0x7f,补码对应的真值是127(383溢除模256)。

    signed char X = -127;
    signed char Y = -2;
    signed char Z = X+Y;
    printf("Z=0x%hhx, %hhd\n", Z, Z);

以下是《C语言深度解剖(第2版)》附录中的C 语言基础测试题5,请思考输出结果。

#include <stdio.h>
#include <string.h>

int main(int argc, char* arg[]) {
    signed char a[1000];
    int i;
    for (i = 0; i<1000; i++)
        a[i] = -1-i;
    printf("%lu", strlen((const char*)a));

    return 0;
}

【例4】已知X=-0000011(-3),Y=-0000010(-2),要求进行补码的加法运算。

  • 直接对补码列竖式计算如下:

        1   1 ( C f C 0 )       [ X ] 补 1   1111101 ( − 3 的补码: 253 ) + ) [ Y ] 补 1   1111110 ( − 2 的补码: 254 )   1   1111011 ( − 5 的补码: 251 ) \begin{array}{c|lcr} \:\:\:\:\: & \: \quad 1\ 1 \qquad\qquad\qquad (C_fC_0)\\ \:\:\:\:\: [X]_补 & \qquad 1\ 1111101 \qquad (-3的补码:253) \\ +) [Y]_补 & \qquad 1\ 1111110 \qquad (-2的补码:254) \\ \hline \ & \qquad 1\ 1111011 \qquad (-5的补码:251) \end{array} [X]+)[Y] 1 1(CfC0)1 1111101(3的补码:253)1 1111110(2的补码:254)1 1111011(5的补码:251)

  • 直接补码运算产生的结果的无符号数值是251,正是预期结果-5的补码!此时,数值部分向符号位产生进位 C 0 = 1 C_0=1 C0=1,符号位向高位产生进位 C f = 1 C_f=1 Cf=1

直观判断,两个小负数相加不会溢出。从单个溢出位判断:

  1. 两个负数相加,总会导致 C f = 1 C_f=1 Cf=1,符号位从负变成正。
  2. 然后 C 0 = 1 C_0=1 C0=1,符号位又从正变成负。

所以,整体上,符号位还是保持为负,即没有溢出。
下面来从数理角度来具体推导一下溢出判断逻辑。

溢出判断

下面基于8位补码,根据符号的异同性,详细讨论上面四个例子对应的符号组合(+、-+、+-、-)时补码加法的溢出条件及判断逻辑。

s1、s2 为补码最高符号位,取值0或1。
x、y 分别为 X、Y 除符号位的其他数值位的真值。

​【例1】一正一负:假设 s1=1,s2=0, 按权展开相加 B 2 T 8 ( X ) + B 2 T 8 ( Y ) = ( − 2 7 + x ) + y = − 2 7 + ( x + y ) B2T_8(X)+B2T_8(Y) = (-2^7+x)+y = -2^7+(x+y) B2T8(X)+B2T8(Y)=(27+x)+y=27+(x+y)

0 ≤ x , y ≤ 2 7 − 1 → 0 ≤ x + y ≤ 2 8 − 2    ⟹    − 2 7 ≤ B 2 T 8 ( X ) + B 2 T 8 ( Y ) ≤ 2 7 − 2 0 \le x,y \le 2^7-1 \to 0 \le x+y \le 2^8-2 \implies \\ -2^7 \le B2T_8(X)+B2T_8(Y) \le 2^7-2 \\ 0x,y2710x+y28227B2T8(X)+B2T8(Y)272

  • 结果 Z 的对应区间为 [-128, 126],不会产生溢出。
  • 另外,s1+s2=1,当 C 0 = 0 C_0=0 C0=0 时, C f = 0 C_f=0 Cf=0;当 C 0 = 1 C_0=1 C0=1 时, C f = 1 C_f=1 Cf=1

【例2】两个正数:s1=s2=0,按权展开相加 B2T_8(X)+B2T_8(Y) = x+y

0 ≤ x , y ≤ 2 7 − 1 → 0 ≤ x + y ≤ 2 8 − 2    ⟹    0 ≤ B 2 T 8 ( X ) + B 2 T 8 ( Y ) ≤ 2 8 − 2 , 0 \le x,y \le 2^7-1 \to 0 \le x+y \le 2^8-2 \implies \\ 0 \le B2T_8(X)+B2T_8(Y) \le 2^8-2, 0x,y2710x+y2820B2T8(X)+B2T8(Y)282

  • 结果 Z 的对应区间为 [0, 254] = [0,127]+[128,254],当结果落入 [128,254] 时,有符号补码真值溢出为负值: Z m o d    256 ≡ Z − 256 Z \mod 256 \equiv Z-256 Zmod256Z256 对应区间 [-128, -2]。

  • 另外,s1+s2=0,我们来看看进位情况:

    • 当 x+y 小于或等于最大正数127(0b11111111)时,无溢出,此时 C 0 = 0 C_0=0 C0=0 C f = 0 C_f=0 Cf=0
    • 当 x+y 大于最大正数127(0b11111111)时,溢出为负,此时 C 0 = 1 C_0=1 C0=1 C f = 0 C_f=0 Cf=0

【例3】两个负数,s1=s2=1,按权展开相加 B 2 T 8 ( X ) + B 2 T 8 ( Y ) = ( − 2 7 + x ) + ( − 2 7 + y ) = − 2 8 + ( x + y ) B2T_8(X)+B2T_8(Y) = (-2^7+x)+(-2^7+y) = -2^8+(x+y) B2T8(X)+B2T8(Y)=(27+x)+(27+y)=28+(x+y)

0 ≤ x , y ≤ 2 7 − 1 → 0 ≤ x + y ≤ 2 8 − 2    ⟹    − 2 8 ≤ B 2 T 8 ( X ) + B 2 T 8 ( Y ) ≤ − 2 0 \le x,y \le 2^7-1 \to 0 \le x+y \le 2^8-2 \implies \\ -2^8 \le B2T_8(X)+B2T_8(Y) \le -2 0x,y2710x+y28228B2T8(X)+B2T8(Y)2

  • 结果 Z 的对应区间为 [-256, -2] = [-256,-129]+[-128,-2],当结果落入 [-256,-129] 时,有符号补码真值溢出为正值: Z m o d    256 ≡ Z + 256 Z \mod 256 \equiv Z+256 Zmod256Z+256 对应区间 [0, 127]。

  • 另外,s1+s2=0( C f = 1 C_f=1 Cf=1),我们来看看进位情况:

    • 当数值部分和(x+y)小于等于127(小于128)时, Z ≤ − 128 Z \le -128 Z128,溢出为正,此时 C 0 = 0 C_0=0 C0=0
    • 当数值部分和(x+y)大于127(大于等于128)时, Z > − 128 Z \gt -128 Z>128,无溢出,此时 C 0 = 1 C_0=1 C0=1

以上例1和例4中,8位正数之和、8位负数之和未超出8位补码表示的数值范围时,没有产生溢出,结果正确。此时,满足 C 0 = C f C_0=C_f C0=Cf,同时为0或同时为1。
以上例2和例3中,8位正数之和、8位负数之和超出8位补码表示的数值范围时,产生了溢出,结果错误。此时,满足 C 0 ≠ C f C_0 \ne C_f C0=Cf,一个为0且一个为1。

综合以上,可用逻辑异或表达式 O F = C f ⊕ C 0 \boxed{OF = C_f \oplus C_0} OF=CfC0 来检验补码加法的溢出情况。

扩展一个数字的位表示

一个常见的运算是在不同字长的整数之间转换,同时又保持数值不变。考虑从一个较小的数据类型转换到一个较大的类型,即扩大位宽。

要将一个无符号数转换成一个更大的数据类型时,只要简单地在开头添加0。具体来说,原数值的字节保持在低(权)位不变,在新增的高(权)位补0。

要将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展(sign extension)。具体来说,原数值的字节保持在低(权)位不变,在新增的高(权)位复制符号位。

  1. 对于正数,高位复制符号位0,不影响其真值。
  2. 对于负数,高位复制符号位1,也不影响真值。

考虑负数的补码位向量 x ⃗ = [ x n − 1 , x n − 2 , … , x 0 ] \vec{x} = [x_{n-1}, x_{n-2} ,\dotsc, x_0] x =[xn1,xn2,,x0],其编码映射如下:

B 2 T n ( x ⃗ ) = ˙ − x n − 1 ∗ 2 n − 1 + ∑ i = 0 n − 2 x i ∗ 2 i B2T_n(\vec{x}) \dot=-x_{n-1}\ast2^{n-1} + \sum_{i=0}^{n-2}x_i\ast2^i B2Tn(x )=˙xn12n1+i=0n2xi2i

当复制高符号位 x n − 1 x_{n-1} xn1 时,位向量 x ′ ⃗ = [ x n − 1 , … , x n − 1 ‾ , x n − 1 , x n − 2 , … , x 0 ] \vec{x'} = [ \textcolor{deepskyblue} { \underline{x_{n-1} ,\dotsc, x_{n-1}} }, x_{n-1}, x_{n-2} ,\dotsc, x_0] x =[xn1,,xn1,xn1,xn2,,x0],则有 B 2 T n ( x ⃗ ) = B 2 T n ′ ( x ′ ⃗ ) B2T_n(\vec{x}) = B2T_{n'}(\vec{x'}) B2Tn(x )=B2Tn(x )


以-89为例,其8位补码位向量为 [ 10100111 ] [1 0100111] [10100111],将其符号扩展为16位的位向量位 [ 11111111 10100111 ] [\textcolor{deepskyblue} { 11111111 } 10100111] [1111111110100111]

B 2 T 8 ( [ 10100111 ] ) = − 1 ∗ 2 7 + 1 ∗ 2 5 + 1 ∗ 2 2 + 1 ∗ 2 1 + 1 ∗ 2 0 B 2 T 16 ( [ 11111111 10100111 ] ) = − 1 ∗ 2 15 + 1 ∗ 2 14 + ⋯ + 1 ∗ 2 8 + 1 ∗ 2 7 + 1 ∗ 2 5 + 1 ∗ 2 2 + 1 ∗ 2 1 + 1 ∗ 2 0 \begin{aligned} & B2T_8([1 0100111]) = -1\ast2^7+1\ast2^5+1\ast2^2+1\ast2^1+1\ast2^0 \\ & B2T_{16}([\textcolor{deepskyblue} { 11111111 } 10100111]) =-1\ast2^{15}+1\ast2^{14}+\cdots+1\ast2^8+1\ast2^7+1\ast2^5+1\ast2^2+1\ast2^1+1\ast2^0 \end{aligned} B2T8([10100111])=127+125+122+121+120B2T16([1111111110100111])=1215+1214++128+127+125+122+121+120

设补码除符号位后的位数值为v,则

B 2 T 8 ( [ 10100111 ] ) = − 1 ∗ 2 7 + v B 2 T 16 ( [ 11111111 10100111 ] ) = − 1 ∗ 2 15 + 1 ∗ 2 14 + ⋯ + 1 ∗ 2 8 + 1 ∗ 2 7 + v \begin{aligned} & B2T_8([1 0100111]) =-1\ast2^7+v \\ & B2T_{16}([\textcolor{deepskyblue} { 11111111 } 10100111]) =-1\ast2^{15}+1\ast2^{14}+\cdots+1\ast2^8+1\ast2^7+v \end{aligned} B2T8([10100111])=127+vB2T16([1111111110100111])=1215+1214++128+127+v

更进一步有:

B 2 T 16 ( [ 11111111 10100111 ] ) = 2 8 ( − 2 7 + 2 6 + ⋯ + 2 0 ) + 2 7 + v = 2 8 ( − 2 7 + ( 2 7 − 1 ) ) + 2 7 + v = − 2 8 + 2 7 + v = − 2 7 + v \begin{aligned} B2T_{16}([\textcolor{deepskyblue} { 11111111 } 10100111]) & = 2^8(-2^7+2^6+\cdots+2^0)+2^7+v \\ & = 2^8(-2^7+(2^7-1))+2^7+v \\ & = -2^8+2^7+v \\ & = -2^7+v \end{aligned} B2T16([1111111110100111])=28(27+26++20)+27+v=28(27+(271))+27+v=28+27+v=27+v

对比可知,符号扩展前后的补码所表示的真值不变。

有符号数和无符号数之间的转换

C语言允许在各种不同的数据类型之间做强制类型转换。例如,假设变量x声明为int,u声明为unsigned。表达式 (unsigned)x 会将x的值转换为一个无符号数值,而 (int)u 将u的值转换为一个有符号整数。

考虑以下代码:

    short int v = -12345;
    unsigned short uv = (unsigned short)v;
    printf("v=%hd, uv=%hu\n", v, uv);
    printf("v=0x%hx, uv=0x%hx\n", v, uv);

在一台采用补码的机器上,上述代码运行输出:

v=-12345, uv=53191
v=0xcfc7, uv=0xcfc7

可以看到,强制类型转换的结果保持位向量(位模组)不变,只是改变了解释这些位的方式。
v=-12345 的补码表示和 uv=53191 的无符号表示是完全一样的。

以上代码片段示例演示了负数补码转换成无符号数。对于负数,补码中最高位作为符号位,本来解释为负权 − 2 n − 1 -2^{n-1} 2n1,按照无符号解释为正权数值位 2 n − 1 2^{n-1} 2n1。由于后面位的数值 v v v 保持不变,相当于在补码表示的真值基础上加上 2 n 2^n 2n

对满足 T M i n n ≤ x ≤ T M a x n TMin_n \le x \le TMax_n TMinnxTMaxn x x x 有:

T 2 U n ( x ) = { x , x ≥ 0 2 n + x ( 2 n − ∣ x ∣ ) , x < 0 T2U_n(x)= \begin{cases} x, & x\ge0 \\ 2^n+x(2^n- \lvert x \rvert), & x<0 \end{cases} T2Un(x)={x,2n+x2nx,x0x<0

简单推导如下:

B 2 U n ( x ) − B 2 T n ( x ) = ( 2 n − 1 + v ) − ( − 2 n − 1 + v ) = 2 ∗ 2 n − 1 = 2 n    ⟹    B 2 U n ( x ) = B 2 T n ( x ) + 2 n \begin{aligned} B2U_n(x) - B2T_n(x) & = (2^{n-1}+v)-(-2^{n-1}+v) = 2\ast2^{n-1}=2^n \\ & \implies B2U_n(x) = B2T_n(x) + 2^n \end{aligned} B2Un(x)B2Tn(x)=(2n1+v)(2n1+v)=22n1=2nB2Un(x)=B2Tn(x)+2n

例如: T 2 U 16 ( − 12345 ) = − 12345 + 2 16 = 53191 T2U_{16}(-12345) = -12345+2^{16}=53191 T2U16(12345)=12345+216=53191


同理,无符号数的最高位解释为正权数值位 2 n − 1 2^{n-1} 2n1,当将其位向量解释成补码时,最高位将作为符号位,解释为负权 − 2 n − 1 -2^{n-1} 2n1。由于后面位的数值 v v v 保持不变,相当于在无符号真值基础上减去 2 n 2^n 2n

对满足 0 ≤ u ≤ U M a x n 0 \le u \le UMax_n 0uUMaxn u u u 有:

U 2 T n ( u ) = { u , u ≤ T M a x n u − 2 n , u > T M a x n U2T_n(u)= \begin{cases} u, & u \le TMax_n \\ u - 2^n, & u>TMax_n \end{cases} U2Tn(u)={u,u2n,uTMaxnu>TMaxn

对于 n = 8 n=8 n=8,当 u ≤ T M a x n = 127 u \le TMax_n = 127 uTMaxn=127 时,即 0 ≤ u ≤ 127 0 \le u \le 127 0u127,刚好落入8位补码所能表示的非负数区间 ;当 u > T M a x n = 127 u \gt TMax_n = 127 u>TMaxn=127 时,例如 128 ≤ u ≤ 255 128 \le u \le 255 128u255,则原始高位1变成负号,补码释义为负数。原来位向量按无符号解释出的真值需减去 2 8 = 256 2^8=256 28=256,换算出按补码解释的正确真值,对应负数区间 [ − 1 , − 128 ] [-1, -128] [1,128]

在数轴上把有符号数和无符号数画出来的话,就能很清晰的看出相对的关系:
有符号&无符号

参考

《深入理解计算机系统》(第3版)
《计算机组成原理》(第3版),唐朔飞
《微机原理与接口技术》(第2版),王克义
《汇编语言程序设计》(第4版),文全刚

深入谈谈二进制
补码表示法的本质原理
【读薄 CSAPP】壹 数据表示
从晶体管开始聊聊计算机为什么采用二进制

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值