转载需首行注明原地址
本章参考 车向泉老师的公开课《计算机系统中的数据表示》
目标:真正理解为什么要有补码,明白补码的各种性质
目录
- 原码
- 模运算
- 补码
- 补码的性质
- 计算机不会存小数点, 多一个小数点,意味着数据需要多占一位,而且计算起来,需要拆分小数点后进行运算,所以计算机存的都是定点数(提前就拆分好,避免运算时拆分)。
而浮点数可以用两个定点数表示,所以以下我们研究的各种存储编“码”都是针对定点数而言。
1. 原码
大家都知道计算机里面存的是2进制,而实际的数据是有正负之分的,为了标识正负,很容易想到给数加上一个进制位。
于是就有了原码,规定加0表示正数,加1表示负数。
-
定义:
整体来讲:
原码就是首位用0表示正数,1表示负数,所以非常简单直观。
这种简单和直观会带来很多个头疼的问题:
1 +0和-0,0不唯一了
2 加减运算及其复杂
例如:要计算 A - B,首先要判断A和B的数的正负和大小,以此来判断最终的正负及运算规则,比如A>B,A+B- (+表示正,A+表示A是正数,同理,B-表示B是负数),那么实际上算A+|B|,如果A<B,A+B+,那么实际上应该算 B-A,结果上放上负号,如果....
无疑,这样的设计放到运算器上,可能今天就没有PC了,那么这种运算最致命的地方在哪里? 为什么会如此复杂?
其根源在于: +-号无法带入运算,这时,数可能是正负,运算可能是加减,同时A,B本身还有大小,则可能出现222共计16种情况。那么如何把+-号带入计算机呢?
首先必须了解计算最基本的运算,然后带入正负,通过正数求负数。
2. 模运算
计算机有个非常讨人厌的且无可避免的情况,“溢出“,因为计算机的字长是有限的,而计算的数可以生成无限的结果。那么当计算出来的数超过字长,应该怎么处理呢?
- 一般来说有两个处理方法,一个返回错误,一个将无法表示的位丢弃(溢出)。
- 返回错误这当然没什么好说,尽管"溢出"同样让人"不爽"。但是还是应该研究溢出,建立“溢出”模型。
于是便有了模运算(这并不是历史,我是瞎推测的)
模运算定义:
在一个运算系统内,若A、B、M满足以下关系:A=B+K*M (K为整数),则记作A≡B(mod M)。
一个很形象的例子:时钟,我把时钟上任意一个时间,拨动一圈,它又回到拨动前的时间。而“溢出”的就是一个天的时间,这和计算机内部非常像。
3. 补码
知道了计算机最基本的运算规则:有模运算。那么应该带入正号来求出负数。
首先,还是规定首位为0就是正数。例如正数A。
那么负数可以看成:-A(0<=A<M,M为模)
已知:A=B+K*M ,可得 :-A = -A + M ,
同时:已知0<=A<M , 可得 :(-A+M) > 0。
这相当于用一个正数 (-A+M) 表示出一个负数 -A 。
同理可得,A = A+M。
由此,可以得出补码的定义:
对于任意一个数 X ,都有X = X + M (X mod M)。
推广到计算机(假设字长为n),可以得到定义:
(注意:负数部分=号,是强制规定,例如8位字长机器码对应10000000,我们强制认定为-128)
4.补码的性质
4.1 补码的符号位
由此可知,首位0一定是正数,首位1一定是负数。
4.2 补码中的0唯一
4.3 补码的范围
假设机器字长为n。
定点小数:
-1 <= x < 1- 2^(-(n-1))
==> 1.0 ~ 0.111....1 (n-1个1)定点整数:
-2^(n-1) <= x <= 2^(n-1)-1
==>1000...0 ~ 0999..9
4.4 补码、真值、原码间的相互转换
4.4.1 真值 => 补码
x为真值
- 当 x >= 0 ,补码==真值==原码
- 当 x <= 0
假设字长为n, |x|代表数值位
小数:
x补 = 2 + x // -1 <= x <= 2^(-(n-1)
= 1+(1-2^(-(n-1)))-|x| + 2^(-(n-1)) // 1+ 0.111...1 - |x| + 0.000...1
= 符号位为一 + |x|全部位按位取反 + 末位加一
//x为-1时,|x|=1,溢出了,结果为0。按位取反+1后继续溢出为0,符号位设置1,结果刚好对-1(主要原因是-1这个是强制认定的值)
整数:
x补 = 2^n + x // -2^(n-1) <= x < 0
=2^(n-1) + (2^(n-1) - 1) - |x| + 1
=符号位为一 + |x|全部位按位取反 + 末位加一
由此,得到一个经验"按位取反,末尾加一"
然而,这个多了一个“加一”,计算机多跑了一次,有办法优化吗?
我们发现,可以从左往右早第一个1,1和1右边的不变,左边的按位取反。
例如 ,-0.10100100 ,可以一步写出答案1.01011100。
4.4.2 补码 => 真值
假设字长为n,x为补码
小数:
x真 = x - 2
= -(1-2^(-(n-1)) - (x-1) + 2^(-(n-1)) // -(0.111..1 - 数值位 + 0.000...1)
= -(补码数值位按位取反 + 0.000...1)
整数:
x真 = x - 2^n
= -(2^(n-1)-1 - (x-2^(n-1)) + 1)// -(111...1 - 数值位 + 1)
=- (补码数值位按位取反 + 1)
同 真值转补码,补码转真值也可以优化:
从左往右早第一个1,1和1右边的不变,左边的按位取反,再加上-号。
4.5 符号的扩展
原则:扩展后,不能影响大小。
正数:
小数尾位补0,整数高位补0,因为补码等于真值,所以扩展后保持不变。
负数:
- 小数:末尾补0
- 整数:高位补1
简单证明下整数:(注意,x为什么可以替换,因为我们是扩展符号位,而x扩展前后必须想等)
4.6 补码的算术右移
所以,定点小数,正数右移高位补0,负数高位补1
我们推导一下定点整数:
正数:
[1/2x补] = 1/2x = 1/2x补
负数:
[1/2x补] = 2^n + 1/2x
=2^n + 1/2(-2^n + x补)
=2^(n-1) + 1/2x补
所以,定点整数,正数右移高位补0,负数高位补1
由此,可以得到结论,正数右移,高位补0,负数右移高位补1。
4.7 补码的算术左移
这里主要的问题:算术左移会让模变大,否则溢出。