前言
人们使用计算机的过程,本质上是人们将生活中的信息,通过数字化技术存储到计算机中,计算机会根据既定的程序处理得到的数字化信息,最终输出人们想要的结果。这个过程中存在着两个很重要的概念:真值和机器数
真值
可以理解为在生活中的各类信息,或者说真正的值。这类信息通常都用带符号的数字表示,进制通常都使用十进制或二进制,其他进制使用较少
真值组成:
- 符号:用+表示正数,用-表示负数
- 数值:十进制/二进制数直接表示
真值举例:+50,-216,+01001000,-11110100
无符号的数?
日常使用的数,都是不带有符号。这是因为正数书写一般可以不用写+号,或者说不带符号的数,一般都认为是正数
机器数
可以理解为真值通过数字化技术转换后,存储在计算机中的二进制数,是另一种形式的真值
之后内容都以8位二进制机器数举例
机器数组成:
符号位(1bit) + 数值位(7bits)
- 符号位:统一用0表示正数,用1表示负数,分别代替真值的+/-号
- 数值位:根据机器数类型的不同,有各自的表达方式
为什么使用机器数呢?
因为电路元件特性的限制,一般都只能表达带电/不带电(有/无,真/假)两种状态,所以计算机系统被设计为只能输入/输出二进制数据。这不仅简化了电路设计,同时还保证了逻辑计算的可靠性以及运算速度。假设最初的计算机被设计为一个十进制系统,可能体积上会比现在要大很多,因为要加入很多额外的电路,元器件
原码 / 反码 / 补码
原码反码补码都是机器数,只不过它们的编码方式不同
原码
最初人们在计算机中尝试使用原码表示数据
原码组成:
- 符号位:同机器数要求
- 数值位:根据真值,直接转换得到的二进制数表示
原码举例:真值+5的原码:0000 0101,真值-5的原码:1000 0101
为什么使用原码?
原码在阅读上是比较符合我们人类习惯的,符号位表示正负,数值位表示绝对值,简洁明了
原码缺陷:
对于人类而言,无论是阅读上,还是计算上,原码都是完美的。对于计算机而言,使用原码的计算上会存在很大的问题:
- 如何进行基础运算?:对于基础四则运算,乘法可以用加法代替,除法可以用减法代替,更进一步如果减法也可以用加法代替,那么计算机电路上会得到最大简化,运算统一使用加法器,问题也就变成了如何将减法用加法表示
- 如何判断结果符号位? :人类进行运算时,会在脑中自动判断正负号,如何让计算机也进行类似的操作,需要额外增加很多电路 ,最好让符号位也能参与加减运算得出,那是最好不过的
反码
反码可以由原码转换得到,是另一种形式的机器数
反码组成:
- 符号位:同机器数要求
- 数值位:正数数值位和原码数值位一样,负数数值位是其原码数值位按位取反(取反即0变1,1变0)
反码举例:真值+5的反码:0000 0101,真值-5的反码:1111 1010
反码意义:主要通过【以进为退】的思想,在一个有限位数位计数系统中,用加法替代减法。原理解析如下:
以常见的时钟为例子,时钟是一位12进制的计数系统,数位取值为1-12。如果现在是5点,计算回退3个小时后的时间是多少,有两种方式:
- 通过减法,5点往回退3个小时,得到2点
- 通过加法,5点往前进9个小时,得到14点,转为12进制,再舍弃高位进位,同样得到2点
以上两种方式转换为基于12进制数学公式,可以得到:
- 通过减法:5 - 3 = 2
- 通过加法:5 - 3 = 5 - 3 + (1 0 - 1)= 5 + (12 - 3) = 1 2 = 2(舍去进位)
公式中值得注意地方是【5 - 3 = 5 - 3 + (1 0 -1)】
这里先说一个新概念:模,模指的是一个有限位数计数系统中刚好能产生溢出的数,也被称为进位基数,模的大小会根据数位取值范围同步发生变化,上述计数系统和数位取值中,模就是13,如果数位取值为0-11,那么模就是12。模的大小在原则上是不会影响到计算结果的,但是计算中需要很注意,容易弄错
可以用模解释【5 - 3 = 5 - 3 + (1 0 -1)】,首先这里的1 0指的是12进制的1 0,转为10进制是13,也就是时钟系统的模,而模-1是一个特殊的值,时钟里任何一个时间点加上/减去模-1,得到的时间点还是它本身,所以这个等价在时钟这个有限数位计数系统中是完全成立的
反码意义拓展:反码意义中的例子,可以类比到8位二进制计数系统,仅考虑数值位,把它想象成可以指向0~111 1111的时钟,一切就都可以解释了。此外可以拓展到负数反码求取公式:网上统一口径都是数值位按位取反,但更严谨的说法应该是:负数的反码 = 模 - 1 - 负数的绝对值,按位取反只是一种取巧的求法/说法
反码缺陷:反码很好的解决了计算机运算加减法的问题,但是依旧没有解决部分原码带来的问题,同时也引入了一部分问题:
- 正负零不相同,如何区分?:在反码中,存在两个零+0:0000 0000/-0:1111 1111,这导致了反码中的负数相加,得到的结果永远都会比预期结果向右偏移1各单位,或者说小1。同时还导致电路设计上需要对额外的零进行单独判断
- 如何判断结果符号位?:原码就存在的老问题,反码中如果将符号位和数值位一并计算,得到的依旧是错误的结果
补码
补码可以由反码转换得到,它是为了解决反码中存在的正负零问题而设计的,同时它还巧妙地解决了符号位判断的问题
补码组成:
符号位:同机器数要求
数值位:正数数值位和原码数值位一样,负数数值位是其反码数值位加上1
补码举例:原码+5的补码:0000 0101,原码-5的补码:1111 1011
补码意义:分为两点
- 通过【偏移】解决正负零问题
在反码中,正数的范围为0000 0000-0111 1111,数值逐渐增大,负数的范围为1111 1111-1000 0000,数值逐渐增大。具体到图形上,将上述正负数绘制到数轴上,可以很明显看出存在两个原点正负零,如果将整体的负数往正数部分挪动一个单位,那么正负数的零就会重合了,抽象到公式,如果负零1111 1111加上1,得到的数就是1 0000 0000,在有限数位计数系统中,舍弃进位恰好就是正零0000 0000!所以补码求法,具体到图形上相当于是将全体负数往正数部分偏移了1个单位,使正负零重合,从抽象数学公式上就是反码数值位+1 - 通过【溢出】,解决符号位计算问题
首先可以考虑换算成补码进行加减运算,什么时候会产生溢出
数值1 | 数值2 | 符号位加和 | 数值位加和后是否产生溢出 |
---|---|---|---|
正数 | 正数 | 0+0=0 | 正常情况,只要结果不超过计数系统的可表示范围,一定不会产生溢出 |
正数 | 负数 | 0+1=1 | 如果如果结果是正数,那么会产生溢出 如果如果结果是负数,那么会不产生溢出 |
负数 | 负数 | 1+1=10= 0(舍弃进位) | 正常情况,只要结果不超过计数系统的可表示范围,一定会产生溢出 |
从上表可以看出,正数负数相加,符号位加和其实都是一定的
更进一步的,如果不忽略对数值位加和溢出,这个溢出如何影响到更高位的符号位
数值1 | 数值2 | 符号位预期 | 考虑溢出的符号位计算结果 |
---|---|---|---|
正数 | 正数 | 0 | 0+0=0 |
正数 | 负数 | 结果正数:0 结果负数:1 | 结果正数:1+1=10=0 结果负数:1+0=1 |
负数 | 负数 | 1 | 0+1=1 |
从上表可以看出,补码进行加减运算时,不用额外忽略数值位溢出,符号位也是正确的。这意味着补码中,符号位可以同数值位一起进行加减运算,得到结果的符号位完全正确!
补码意义拓展:需要明确的一点是,补码是从反码中偏移一个单位得来的,没有进行额外的运算,所以补码依旧拥有反码的特性:用加法代替减法。偏移操作不仅消除了正负零存在,而且使得对负数相加结果数值上有了偏移,可以得到正确的结果
此外对于补码正数+负数溢出的产生,单纯看表格解析的还是有些抽象的,可以从数轴上去考虑,加减法操作可以看作是将一个数向数轴的正/负方向进行移动另一个数长度的距离,如果移动过程中,跨越了正负零,那么由于数轴偏移肯定会产生溢出,正数+正数,负数+负数都比较好理解,超出计数系统可表示范围,那么肯定会产生溢出