零、参考资料
Under-One、一些技术概念
机器数与真值
- 真值:在日常的书写习惯中,往往用正、负号加绝对值表示数值,用这种形式表示的数值为真值。例如100D,-50D,-76O等
- 机器数:在计算机内部中使用的数,并且最高位表示符号的二进制数,又分为有符号数和无符号数。例如正数 7 用 8 位二进制数来表示:0000 0111
- 机器数与真值之间的转换会使用一些原/反/补码的概念
计算
- 计算机中统一使用的是加法计算 - 计算机不会减法
一、问题的产生 - 机器数的运算效率问题
问题:机器数是带符号的,而两个数相加,符号位是否参加运算,以及是如何运算的?- 两个正数相加(均以原码表示):
0000, 1110 + 0000, 0001 = 0000, 1111 // 14 + 1 = 15 正确 ^ ^ ^ 符号位 符号位 符号位
- 两个负数相加(均以原码表示):
1000, 1110 + 1000, 0001 = 0000, 1111 // (-14) + (-1) = 15 错误 ^ ^ ^ 符号位 符号位 符号位(最高位的 1 溢出)
- 两个负数相加:
- 用较大的绝对值 + 较小的绝对值(加法运算)
- 最终结果的符号为负
- 正负数相加:
- 判断两个数的绝对值大小(数值部分)
- 用较大的绝对值 - 较小的绝对值(减法运算)
- 最终结果的符号取绝对值较大数的符号
二、原码、反码、补码
为了解决有符号机器数运算效率问题,计算机科学家们提出多种机器数的表示法:机器数 | 正数 | 负数 |
原码 | 符号位表示符号 数值位表示真值的绝对值 |
符号位表示数字的符号 数值位表示真值的绝对值 |
反码 | 无(或者认为是原码本身) | 符号位为 1 数值位是对原码数值位的 “按位取反” |
补码 | 无(或者认为是原码本身) | 在负数反码的基础上 + 1 |
- 原码:最简单的机器数,例如前文提到从 +1110 和 -1110 转换得到的 0000, 1110 和 1000, 1110 就是原码表示法,所以原码在进行数字运算时会存在前文提到的效率问题
- 反码:一般认为是原码和补码转换的中间过渡
- 补码:补码才是解决机器数的运算效率的关键,在计算机中所有 "整型类型" 的负数都会使用补码表示法
求负数补码的步骤:
- 得到这个负数的原码,如 -14 的原码是:1000 1110
- 保留符号位不变,将数值位取反,得到反码:1111 0001
- 将反码 + 1,得到补码:1111 0010
1111, 0010 + 1111, 1111 = 1111, 0001 // (-14) + (-1) = (-15) 正确 ^ ^ ^ 符号位 符号位 符号位(最高位的 1 溢出)由此,使用补码表示法后,有符号机器数加法运算就只是纯粹的加法运算,不会因为符号的正负性而采用不同的计算方法,也不需要减法运算,简化了电路设计 同时,除了消除减法运算外,补码表示法还实现了 "0" 的机器数的唯一性: 在原码表示法中,"+0" 和 "-0" 都是合法的,而在补码表示法中 "0" 只有唯一的机器数表示,即 "0000, 0000" 。换言之补码能够比原码多表示一个最小的负数 1000, 0000
三、补码背后的规律
具体与数学中的补数相关,这里简单举个例子来描述下规律:时钟的例子
比如说,现在时钟的时针刻度指向 6 点,我们想让它指向 3 点,应该怎么做?- 方法 1:逆时针地拨动 3 个点数,让时针指向 3 点,这相当于做减法运算 -3
- 方法 2:顺时针地拨动 9 个点数,让时针指向 3 点,这相当于做加法运算 +9
十进制的一个例子
如果计算 354365 - 95937,用补数的规律该怎么做?354365 - 95937 // = 258428 = 354365 - (1000000 - 904063) = 354365 - 1000000 + 904063 // 减整加补 = 258428
这里存在的问题是:为何是 1000000 ?这个 1000000 就相当于是时钟中的 12,是当前运算中的最大位数上限(周期之类的概念),这个数据似乎还和进制相关(这个有空再研究),所以实际上,运算的过程是:
354365 + 904063 = 1258428 = 258428 ^ 最高位 1 超出位数限制,直接丢弃
总结
补码的关键在于:找到一个与负数等价的正补数,使用该正补数代替负数,从而将减法运算替换为两个正数加法运算。 补码的出现与运算器的电路设计有关,从设计者的角度看,希望尽可能简化电路设计和计算复杂度。而使用正补数代替负数就可以消除减法器,实现简化电路的目的四、位运算
位运算是基于二进制值的运算(补码表现形式)Operator | Usage | Description |
按位与 | a & b | 在 a,b 的位表示中,每一个对应的位都为 1 则返回 1,否则返回 0 |
按位或 | a | b | 在 a,b 的位表示中,每一个对应的位,只要有一个为 1 则返回 1,否则返回 0 |
按位异或 | a ^ b | 在 a,b 的位表示中,每一个对应的位,两个不相同则返回 1,相同则返回 0 |
按位非 | ~ a | 反转被操作数的位 |
左移 | a << b | 将 a 的二进制串向左移动 b 位,右边移入 0 |
算术右移(有符号右移,其实就是保持符号位不变,数值位右移) | a >> b | 把 a 的二进制表示向右移动 b 位,丢弃被移出的所有位 |
无符号右移(左边空出位用 0 填充) | a >>> b | 把 a 的二进制表示向右移动 b 位,丢弃被移出的所有位,并把左边空出的位都填充为 0 |