第一章中已经讲过一些二进制数与十六进制数的知识,现在我们要对已有的知识做些扩充,最重要的一个问题就是二进制数是如何表示"符号"的。
这个问题并不复杂,表示一个带符号的整数常用的方法有三种:原码、反码表示法和补码表示法。先来看看原码表示法。在计算机中,数的符号是用一个数位来表示的,一般用数的最高位。正号用0表示,负号用1表示。所谓原码,就是简单地遵循这一规定的一种表示法。例如我们用原码表示+1,可以写成00000001,其最高位是0,表明这个数的符号是"+"。如果表示-1,则可以写成"10000001",最高位的"1"就表示其符号为"―"。这种表示带符号数的方法法就是原码表示法。
反码比原码复杂一些,它规定若一个数值为正,则它的反码和原码形式相同。如+1仍写成"00000001";若一个数值为负,则反码的符号位为1,其余各位对原码取反。如-1写成"11111110";这两种方法在计算机中很少采用,原因很简单,原码和反码不便于运算。举个例子:用原码计算-1+1=?
问题似乎不难,但需要考虑的事情很多。假如只是简单地在"10000001"的最低位加上1,那么将得到结果--10000010,根据原码的规定,这个结果是-2。
要想得到正确结果,我们必须首先要考虑将符号位置0,同时最低位也不能加1,而要减1。即使采用反码计算,也要单独处理其符号。这样计算不仅对我们自己,就是对CPU来说也是不方便的。因此,多数机器都采用补码表示法。
在补码表示法中对于负数的表达要比反码麻烦一些,负数X用"2n-|X|"表示,其中"n"是数的位数。对于八位二进制数来讲n=8,因此用八位二进制补码表示-1就是28-1=11111111,也就是十六进制数0FFH。正数的表示方法和原码一样,+1也写成"00000001"。
由此我们可以发现正负数之间具有这样一种转换关系:将+1的所有位取反得到"11111110",再在最低位上加1就得到"11111111",也就是-1。同时我们也能看出补码表示法中关于符号位的规定和原码是一样的。
那么"10000001"在补码表示法中是哪个数呢?按照刚才发现的规律,将它的各个位取反,得"01111110",再加上1,得"01111111",即十进制的+127,也就是说"10000001"表示-127。
为什么要用这样的表示法,这主要是因为补码便于计算。我们可以用补码重新计算-1+1=?
由于-1的补码是"11111111",将其加1,会得到"100000000",这是一个九位二进制数,如果舍掉最高位,就得到正确的结果--00000000。
这似乎有点不讲理,凭什么舍去最高位呢?道理其实很简单,我们先来看看是否真有这样的现象,启动DEBUG输入下面两条指令:
C:\ASM\>DEBUG[Enter] -a100[Enter] 0F6A:0100 mov al,ff 0F6A:0102 add al,1 0F6A:0104 [Enter] -r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0100 NV UP EI PL NZ NA PO NC 0F6A:0100 B0FF MOV AL,FF -t AX=00FF BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0102 NV UP EI PL NZ NA PO NC 0F6A:0102 0401 ADD AL,01 -t AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0104 NV UP EI PL NZ AC PO CY 0F6A:0104 56 PUSH SI
可以看到将FF(-1)加1之后AL确实成了0。AL是八位寄存器,它不可以记录第九位,因而在AL寄存器中只保留了低八位。
那么是不是多出的一位就无影无踪了呢?并非如此,如果仔细观察DEBUG显示出的内容,就会发现标志寄存器中有一些位发生了变化:AF(辅助进位标志)和CF(借位/进位标志)被置成1了。
由于我们采用了八位寄存器,运算结果产生了第九位,这一位作为进位送入了CF标志位。至于AF,它记录了AL寄存器低"四"位的进位情况。请看下面的实例:
C:\ASM\>DEBUG[Enter] -a100[Enter] 0B0B:0100 mov al,f 0B0B:0102 inc al 0B0B:0104 -r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0B0B ES=0B0B SS=0B0B CS=0B0B IP=0100 NV UP EI PL NZ NA PO NC 0B0B:0100 B00F MOV AL,0F -t AX=00FF BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0B0B ES=0B0B SS=0B0B CS=0B0B IP=0102 NV UP EI PL NZ NA PO NC 0B0B:0102 FEC0 INC AL -t AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0B0B ES=0B0B SS=0B0B CS=0B0B IP=0104 NV UP EI PL NZ AC PO NC 0B0B:0104 7463 JZ 0169
低四位产生进位的情况由AF反映出来。
采用补码表示法还有一个好处,它可以把加、减法统一成加法,很容易看出1-1和1+FF(-1)的实际结果是一样的。至于补码的乘除法运算这里不再多讲,查阅有关书籍即可掌握。
明白了数字的表示方法,那么我们在前面讨论的移位与乘除法的问题也就迎刃而解了。用"SHL/SHR"指令移位会使符号位发生变化,所以负数无法用这两条指令完成乘除计算。
现在我们从"带符号数"的角度来看0F0H与0FFF0H这两个数,可以看出它们的确是相等的。在计算机技术中数的符号是一个比较微妙的问题,就拿0FFH这个数来说,作为一个"无符号数"(最高位不表示符号)它是+255,而作为"带符号数"它就成了-1。那么计算机究竟把它当成什么呢?这个问题现在讨论它还为时过早,在后面的章节里会对这个问题做补充说明。总之,补码的知识是很重要的,大家应该熟练地掌握。