负数的存储-原码 反码 补码
在计算机中,数字是用二进制来表示的,使用二进制的方式考虑到两点 1. 问题简单化 电路简单 2. 计算简单化 二进制特性使然。在计算机中 数字是有限位的这是与数学概念不同的地方,比如在32位系统中 char 类型表示1个byte 1个byte由8个bit组成,这8个bit的数字表达能力(能表示的不同状态)是 2^8 = 256 个,也就是说1个byte能表达的数字范围是 1 ~ 256 考虑到0的特殊性 需要把 0 也加进来 这时候的表达范围是 0 ~ 255 。对于自然数 存在负数的形式 需要将一半的数据拿来表达负数,这个时候1byte能表达有符号数字的范围是 -1 ~ -128 + 0 ~ 127 也就是 -128 ~ 127 ,这个是概念 在这些比特位上如何去实现这样的设想呢,目前的计算机中是将正数和零 正常存储(也就是存在低位)负数通过补码
的形式存储(存储在高位) 且 补码的求法是 补码 = 反码 + 1
。
其实对于这个 补码
的来历以及求法很疑惑,并不清楚通过什么逻辑得到的,虽然这样的设计非常巧妙 这样的存储形式 可以直接用原码
的形式进行简单的加法计算,这样也进一步降低的计算机逻辑电路的复杂度,通过补码
这种存储形式 可以将所有的计算看作加法这一种形式。补码
在帕斯卡的机械计算器中也有用到,也许是第一次使用补码的形式把。
接下来我要一步步的分析下 前辈是如何发现并利用这样一个数字特性的(这并不是发明 这是数字存在的特性 我们是发现了这些特性并使用在了计算机上),8bit太多 这里简化问题 使用 3bit来分析。首先需要说明几个概念 ,既然我们是利用这些 bit 位实现对正负数的表达 就涉及到一些变化,对于最终在计算机上的存储形式 称作 原码
而这个bit组合状态 所表达的实际值 称作 表达值
。
3bit所能表达的状态如下:
1 | 000 001 010 011 100 101 110 111 |
考虑到负数的引入,这里需要拿出一半的状态用来表示负数,最先想到的方式 是 数学中数轴的表达方式,如下:
1 | 000 001 010 011 100 101 110 111 |
这时有一个位是没有用到的,可以先暂时忽略,关注主体。其实这里的数字是可以直接用来计算了,可以用来计算的原因是他们按照数学计算逻辑中的大小顺序排列的。其中 表达值 = 原码 - 状态总数/2
这样是可以直接通过原码进行计算的,证明过程如下:
1 | 假设: |
表达值的计算最终可以转换成原码的计算,但需要减去状态总数,这其实多了一步转换,且这些转换对于正数负数都需要,对这个排列进行优化 会想到将正数放在低位 负数放在高位,这样正数的表达值 = 原码 ,需要转换处理的是负数,这样得到如下结果:
1 | 000 001 010 011 100 101 110 111 |
这样的排列形式还是可以直接进行运算的,因为其核心 只是将正负数的连续数字部分做了偏移 这些偏移是可以通过等价条件相互转换的,分析这个转换过程:已知之前的转换条件是 表达值 = 原码 - 状态总数/2
我们需要将正数与负数的位置进行调换,这时候需要分开对正数与负数的变换过程进行分析:
1 | 对于之前的表达值与原码的关系可得 |
上面已经说了,位置的调整不会影响到计算,我们也通过转换关系改变了 表达值 与 原码的转换关系,这个时候数字的可计算性是没有变化的,也就是说 通过这个转换过程我们就能通过表达值的计算转换成 原码的计算。
虽然,经过上一步的位移 我们减少了对正数的转换,但是负数还是需要转换,且增加了对正数负数情况的区别,看起来并没有简化问题。但是对于负数的情况我们需要单独分析下,表达式 = 原码 - 状态总数
这个公式 中的 原码 - 状态总数
其实就是 原码
其中的 - 状态总数
并无意义。这要说起计算机的数字是有限范围的 而数学中的数字是没有范围的(他是概念的形式存在的),对于数字有范围的计算机 当其值超出其所能表达的范围外的时候 这个计数会从 零 重新开始。
数学上的数轴是从无限小到无限大 , 计算机中的这个数轴更像是钟表的环形样式,超过十二点就是一点了。所以任何值 加上或减去 状态总数 其实就等于其本身。由于这样一个特性 我们可以将上面负数的转换过程进行简化调整,
1 | 表达值 = 原码 - 状态总数 = 原码 |
进而,对于正数、负数都成立的 表达式 = 原码
,也就是 可以直接参与计算了 直接用原码进行数学元算即可!
这其中的负数那部分 原码 其实是被叫做 补码
的 , 他特指负数的原码,关于 补码
的推导过程如下:
1 | 假设 |
最终得到 x‘ = ^(-x) + 1
由于 x 是负数 -x 是这个负数的绝对值 也就是正数 ^(-x) 也就是负数对应的正数的反码
原码 = 表达值绝对值的反码 + 1
也就是现在常说的 补码 = 反码 + 1