细说计算机底层整型编码机制

现在计算机的抽象级别愈来愈高,愈加少人关注在计算机底层发生了什么事情,其实底层也有些颇有意思的东西。这篇文章主要想科普一下整型在计算机硬件中的相关实现,它是以什么方式来存储的?如何区别正负数?硬件会怎么去解释相关的位串?编程

无符号数

在现代编程语言中,支持无符号数的语言已经比较少了。常见的就只有C/C++,它们能够经过unsigned关键字定义无符号相关的数据类型。无符号整型与有符号整型最大的区别就是对相同的二进制位串解读方式不同。全部的无符号数都是非负数,由于它是没有符号的。若是咱们要用位长为w的空间来存储整型,以无符号的方式来解读的话第k位的权重都是2 ^ k,其中最高有效位的权重为2 ^ (w - 1)bash

举个具体点的例子,假设二进制串11000001所表明的是一个无符号数,因为它是8位长,因此最高有效位的权重是2 ^ (8 - 1) = 128,第0位的权重是2 ^ 0 = 1, 第一位的权重是2 ^ 1 = 2,以此类推。最终代入公式可得编程语言

2 ^ 7 * 1 + 2 ^ 6 * 1 + 2 ^ 5 * 0 + 2 ^ 4 * 0 + 2 ^ 3 * 0 + 2 ^ 2 * 0 + 2 ^ 1 * 0 + 2 ^ 0 * 1 = 193
复制代码

所以,二进制串11000001所对应的无符号数为193。还能够猜想出在8位的空间内最小的无符号数是0,它的二进制表示为00000000,最大的无符号数是255,它的二进制表示为11111111编码

虽然说,在平常的业务逻辑中用到无符号数的机会已经很少了,不过在计算机底层你仍是能够常常看到它的身影。假设咱们要用如下的16位的二进制串来表明整型spa

11100000 10000000
复制代码

其中高8位为11100000,低8位为10000000。无论这个16位的位串最终会被解读成有符号数仍是无符号数,低8位始终均可以看做一个无符号数128。同理,若是是一个表明整型的32位二进制串code

11100000 10000000 11100000 10000000
复制代码

那么它的低24位就能够看做一个无符号数了。从这个角度来看,虽然说咱们平时不会直接操做无符号数,但实际上无符号数在计算机底层仍是“大量存在”的。数学

有符号数-原码,反码,补码

若是在整型的世界中只有无符号数的话,那么彷佛难以构建一个庞大的系统,毕竟有许多数据或者运算都要借助负数。接下来我想简单介绍一下在计算机底层要表示一个有符号的整型主要有哪些策略。it

1. 原码(Sign-Magnitude)

这种编码方式比较好理解,前面所说的无符号数之因此没法表示负数,那是由于咱们并无留下一个特殊的位置用于标识它究竟是大于0的仍是小于0的。而原码的基本原理其实就是分配一个位用来标识该数究竟是正数仍是负数class

一样用8位空间来举例子,采用最高有效位来做为符号位,其余位用于权重计算,那么不难看出它所能表示的最大的数就是01111111,最小的数就是11111111。它们的值分别为127-127,不过现在的大多数机器都不是以这种方式来表示有符号整型的。并且这种方式还有个比较突出的特性,若是要表示数字0,将有两种二进制编码方式,+0会表示为00000000,-0会表示为10000000。也许这种不惟一性也是它没有在整型的领域被普遍采用的缘由吧。不过原码这种编码方式在浮点数里面会用到,这个之后有机会再说。原理

2. 反码(Ones' complement)

记得是小学数学里面就已经出现了减法运算了,可是我是到了初中才接触到负数的概念。一个负数其实就是某个特定正数的相反数。正数56的取反结果就是-56,那么在二进制里面怎么取反呢?一个可行的办法就是按位取反,这也是反码的最直观的特征。00111000所表明的数值是56,若是要用反码来表示-56直接把56的二进制串按位取反便可11000111

不过上面所说的只是反码给人最直观的感受,实际上它仍是须要必定的数学支持的。在反码里面最高有效位的权重为- (2 ^ (w - 1) - 1),若是是一个8位的位串,最高有效位的权重为-127,其余位的计算方式跟无符号数同样。那么把前面所得的11000111二进制串代入公式可得

-(2 ^ 7 - 1) * 1 + 2 ^ 6 * 1 + 2 ^ 5 * 0 + 2 ^ 4 * 0 + 2 ^ 3 * 0 + 2 ^ 2 * 1 + 2 ^ 1 * 1 + 2 ^ 0 * 1 = -56
复制代码

结果恰好是-56。这个时候,不难推断出一个字节所能表示的最大值为127(01111111),最小值为-127(10000000)。反码所可以表示的数值范围跟原码同样,只是某些数值在底层的编码方式会有所不一样。此外,它跟原码都有一样的问题,就是对数字0有两种不一样的二进制表示方式。在反码中+0表示为00000000,-0表示为11111111

3. 补码(Two's complement)

理论上,原码和反码都可以用来表示有符号数,不过他们都有奇怪的属性,就是对数字0分别会有两种不一样的位模式。而这里要说的补码就能很好地解决这种问题。

与原码,反码相似,补码也是要靠最高有效位来影响数值的正负。对于长度为w位的补码表示,最高有效位为w - 1,最高有效位的权重为2 ^ (w - 1)。不难推断出在一个字节长的空间内,补码所可以表示的有符号数最大值为127(011111111),最小值为-128(11111111)。

若是要用补码来表示整型56,它所对应的二进制串依然是00111000。若是要表示-56则有

- 2 ^ 7 * 1 + 2 ^ 6 * 1 + 2 ^ 5 * 0 + 2 ^ 4 * 0 + 2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 0 + 2 ^ 0 * 0 = -56
复制代码

所以,-56的补码表示为11001000。在补码下-5656两个数值对应的位模式之间有如下关系

11001000 + 00111000 = 1 00000000
复制代码

它们相加刚好等于1 00000000,然而在底层咱们只用了一个字节的空间来存储它们相加的结果,所以须要对最终结果进行截断处理。最终获得的结果是00000000。是否是有点意思?截断以后刚好就是56 + (-56) = 0,这也是补码优雅之处。

与原码,反码相比,补码较大的好处就是映射具备惟一性,对于数值0不会再有两种不一样的表示方式了。在位长为w的空间中,补码可以多表示一个数值- 2 ^ (w - 1)。此外,它能表示的数值刚好一半是非负数0 ~ 2 ^ (w - 1) - 1,另外一半是负数- (2 ^ (w - 1)) ~ -1。对8位二进制串来讲则是非负数范围是0 ~ 127,负数范围是-128 ~ -1

无符号与补码的关系

在相同的位模式下,无符号表示与补码表示最大的区别就是最高有效位的权重不一样。位长为w的无符号表示中,最高有效位的权重为2 ^ (w - 1),而补码表示的最高有效位权重为-2 ^ (w - 1)。当最高有效位为0时,它们所表示的数值相同。只有在最高有效位为1的时候,二者之间所表示的数值才会有所不一样,这个时候它们所表示的数值之间相差|2 ^ (w - 1) - (- 2 ^ (w - 1))| = 2 ^ w

简单举两个例子

a. 最高位为0

以无符号的方式来解读二进制位模式00011110,代入公式可得

2 ^ 7 * 0 + 2 ^ 6 * 0 + 2 + 2 ^ 5 * 0 + 2 ^ 4 * 1 + 2 ^ 3 * 1 + 2 ^ 2 * 1 + 2 ^ 1 * 1 + 2 ^ 0 * 0 = 30
复制代码

以补码的方式来解读可得

-2 ^ 7 * 0 + 2 ^ 6 * 0 + 2 + 2 ^ 5 * 0 + 2 ^ 4 * 1 + 2 ^ 3 * 1 + 2 ^ 2 * 1 + 2 ^ 1 * 1 + 2 ^ 0 * 0 = 30
复制代码

此时最高有效位为0,所以它的权重对大局来讲没有任何影响。二进制位模式00011110不管是以无符号的方式去解读仍是以补码,原码,反码的方式去解读,它所表明的数值都是30

b. 最高位为1

当最高有效位为1的时候二者之间差异就比较大了。这个时候补码所表示的老是负数。

对二进制位模式10001001,以无符号的形式进行转换,代入公式可得

2 ^ 7 * 1 + 2 ^ 6 * 0 + 2 ^ 5 * 0 + 2 ^ 4 * 0 + 2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 0 + 2 ^ 0 * 1 = 137
复制代码

而以补码的方式去解读则会有

- 2 ^ 7 * 1 + 2 ^ 6 * 0 + 2 ^ 5 * 0 + 2 ^ 4 * 0 + 2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 0 + 2 ^ 0 * 1 = -119
复制代码

两种换算方式所获得的数值相差甚远,前面推导过二者相差2 ^ w。这个例子中就是137 - (-119) = 256,刚好是2 ^ 8

总结

这篇文章主要简单介绍一下在要用二进制来表示整数有哪几种不一样的编码策略,虽然说大多数状况下咱们都不须要关心底层到底发生了什么(除非你是在用C/C++来编写代码),不过看成简单的科普知识来了解一下仍是挺有趣的。

相关文章
相关标签/搜索