数的编码
一、机器数
就是将数学里实数系的数在计算机里的二进制表现形式称为机器数。在数学里,数有正负之分,正数前面加个(+)这个符号来表示(一般不加),负数前面一定要加上符号(-)来表示,比如2位10进制数正10可以表示位+10,负10表示为-10。我们可以看到,当加上符号后,由2位数变成了3位数,加了一个符号位。那计算机里怎么表示呢?我们知道计算机里肯定是用二进制来表达,同时计算机里的数是有位数限制的,通常都是一个字节的倍数。比如8位整数,16位、32位等。由于数有正负之分,我们又不能突破这个位数的限制,所以只能在固定的位数里取最高位为符号位,1---负数,0---正数,剩下的位数来存储数值,这就是数的机器数编码。比如一个8位的整数,它的位域如下:
机器数编码有以下两个特点:
- 用二进制存储
- 长度有限制,受机器字长限制
- 数的符号数值化:1---负,0---正
我们看到,机器数已然是编码了,编码规则为最高位为符号位,剩下的的是数值位。由于这个编码规则,导致一个机器数在我们人看来就出现了如下两个值:
- 真值
当我们把机器数当做有符号位的二进制数值去计算,得到的值就是机器数真实所表示的值,称之为真值 - 形式值
当我们把机器数当做数学角度的没有符号位的二进制数去计算,得到的值只是一个纯数学角度的数值,并非机器数真实代表的值,称之为形式值
由于机器数是带有符号位的,纯数据上的二进制数是没有符号位的。所以机器数在计算机中所表示的真实值和形式值是不一样的。比如有符号的机器数10000011,真值是十进制的-3,形式值是131
二、数值编码
通过符号位编码后,计算机里的数还要对数值位进行编码,才能顺利完成数值的计算功能。现在对数的编码存在如下几种编码:原码、反码、补码、移码。其实计算机里数的表达方式使用的是补码和移码,整数使用的是补码,实数使用的是移码。
2.1、原码
2.1.1、什么是原码
是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。数值位存放该数的二进制的绝对值。人脑最容易理解和计算的表示方式,通过原码,我们可以快速的知道其对应的真值是什么,这就是需要原码的主要原因。
整数的源码表示范围:-(2n-1-1)~+(2n-1-1)
比如8位整数:(+1)10--->00000001
(-1)10--->10000001
我们来看下 (0)10的原码
00000000--->(+0)10
10000000--->(-0)10
居然出现了+0和-0,这在数学学科是不会出现的。
2.1.2、原码的运算:
在结果不超出位数限制内,我们做正数的加法
比如做(+1)10+(+1)10=(00000001)2原+(00000001)2原=(00000010)2原=(2)10,继续做下去,我们发现正数的加法很简单顺利。
在结果不超出位数限制内,我们做正负数的加法看看
比如做(+1)10+(-1)10=(00000001)2原+(10000001)2原=(10000010)2原=(-2)10,这什么鬼,出问题了,看来用原码正负数的加法,也就是减法会有问题。
2.1.3、存在的问题
通过前面的介绍,我们发现原码存在着两个显著的问题:
- 数的表达冲突:正0和负0同时存在
- 运算的问题:减法运算的问题
2.2、反码
为了解决原码存在的问题,人们想到了反码。
2.2.1、什么是反码
反码是在原码的基础上,符号位不变,数值位全部取反的结果。整数的反码就是原码,负数的反码是符号位不变,数值位全部取反,也就是说只有负数有反码。
整数的反码表示范围:-(2n-1-1)~+(2n-1-1)
比如8位整数:(+1)10--->00000001
(-1)10--->11111110
我们来看下 (0)10的反码
00000000--->(+0)10
111111111--->(-0)10
也出现了+0和-0的问题。
2.2.2、反码运算
在结果不超出位数限制内,我们做正数的加法
比如做(+1)10+(+1)10=(00000001)2反+(00000001)2反=(00000010)2反=(2)10,继续做下去,我们发现正数的加法很简单顺利。
在结果不超出位数限制内,我们做正负数的加法看看
比如做(+1)10+(-1)10=(00000001)2反+(11111110)2反=(11111111)2反=(-0)10,看来解决了正负数的加法运算问题,也就是减法运算。
在看看两个负数的相加会怎样
比如做(-1)10+(-1)10=(11111110)2反+(11111110)2反=(10000000)2反=(-0)10,出错了
在来(-1)10+(-2)10=(11111110)2反+(11111101)2反=(111111011)2反=(-4)10,也出错了,看来反码对两个负数的加法运算也有问题。
2.2.3、存在的问题
- 同样出现正0和负0同时存在的问题
- 运算问题:负数加法运算问题
2.3、补码
反码解决了减法的问题,但是由符号位带来的问题还是没由得到完美处理,于是人们又想到了补码。
2.3.1、补码是什么
正数的补码也还是原码,负数的补码是在原码的基础上取反码,在加1就可。
整数的补码表示范围:-(2n-1)~+(2n-1-1)
比如8位整数:(+1)10--->00000001
(-1)10--->11111111
我们来看下 (0)10的反码
(+0)10---> (00000000)2原--->(01111111)2反--->(00000000)2补--->(+0)10
(-0)10---> (10000000)2原--->(11111111)2反--->(00000000)2补--->(+0)10丢掉进位
可以看到解决了两个0的问题了。
2.3.2、补码运算
补码运算最重要的一点是符号位参与运算。
在结果不超出位数限制内,我们做正数的加法
比如做(+1)10+(+1)10=(00000001)2补+(00000001)2补=(00000010)2补=(2)10,继续做下去,我们发现正数的加法很简单顺利。
在结果不超出位数限制内,我们做正负数的加法看看
比如做(+1)10+(-1)10=(00000001)2补+(11111111)2补=(00000000)2补=(0)10,没问题。
在看看两个负数的相加会怎样
比如做(-1)10+(-1)10=(11111111)2补+(11111111)2补=(11111110)2补=(-2)10,没问题
在来(-1)10+(-2)10=(11111111)2补+(11111110)2补=(111111101)2补=(-3)10,没问题。
3、总结
- 原码就是计算机为了存储带符号的二进制数值而出现的机器数形式
- 反码就是为了解决原码无法进行减法运算的问题,说白了就是为了解决与负数相加的问题
- 补码就是为了解决反码遗留的正负0问题而出现的新机器数形式
在现代计算里,数值的存储和计算都是用的补码。
三、整数编码
有符号整数的编码完全就是按照补码编码的,无符号整数其实是按存数学意义上的正整数二进制表达的。
四、实数的编码
4.1、浮点数和定点数
在计算机系统的发展过程中,曾经提出过多种方法表达实数。典型的比如相对于浮点数的定点数(Fixed Point Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。还有一种提议的表达方式为有理数表达方式,即用两个整数的比值来表达实数。
定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:±d.dd...d × β e , (0 ≤ d i < β)。其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p 来表示。每个数字 d 介于 0 和基数之间,包括 0。小数点左侧的数字不为 0。
4.2、计算机里的浮点数---IEEE 浮点数
计算机中是用有限的连续字节保存浮点数的,采用 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的编码在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。
4.2.1 移码
移码常用来表示浮点数的的指数部分。在IEEE规定中译码定义如下:
(X)移=2n+X
X为真值,其中X为真值,与补码相比,数值位完全相同,符号位相反。也就是说移码是真值加一个偏移值得到的。
单精度的指数为8位,那么原来8位二进制数有符号二进制表示范围是-127~127,但是IEEE为了不在指数中引入符号位,且8位指数(指数中二进制数为无符号数)范围是1~254,因此引入偏移码(移码),在有符号位的二进制基础上加127,那么就有了偏移阶码范围0~254,但是0不合法,因此偏移阶码范围是1~254。为什么是127呢?加上127不就从原来的二进制有符号转变为无符号了嘛。IEEE不就是为了不在指数中不引入符号位才加的127嘛。
填坑,为什么范围是1~254?因为0用8位阶码用全0表示,255用8位阶码用全1。 出现全0,尾数M全0,符号位为0,为正0,符号位为1,为负0(IEEE754规定的浮点数有正0负0之分),偏移阶码出现全1,尾数M全0,符号位S为0,为正无穷大,符号位S为1,为负无穷大。 这就是为什么不把0和255放入偏移阶码范围中的原因。即:全尾数全0或全1时表示其他特殊意义的数。
4.2.2、单精度浮点数
单精度浮点数是用32bit来存储一个实数,它的位域如下:
- 符号位
最高位作为符号位,1---负数,0---整数 - 指数域
紧接着符号位的8位存储指数部分。8 位的指数为可以表达 0 到 255 之间的 255 个指数值。但是,指数可以为正数,也可以为负数。为了处理负指数的情况,实际的指数值按要求需要加上一个偏差(Bias)值作为保存在指数域中的值,单精度数的偏差值为 127。单精度的实际指数值 0 在指数域中将保存为 127;而保存在指数域中的 64 则表示实际的指数值 -63。 偏差的引入使得对于单精度数,实际可以表达的指数值的范围就变成 -127 到 128 之间(包含两端)。我们不久还将看到,实际的指数值 -127(保存为 全 0)以及 +128(保存为全 1)保留用作特殊值的处理。这样,实际可以表达的有效指数范围就在 -127 和 127 之间。 - 尾数域(有效数字)
剩下的23位用来保存尾数。IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为 1,因此我们在保存尾数的时候,可以省略小数点前面这个 1,从而腾出一个二进制位来保存更多的尾数。这样我们实际上用 23 位长的尾数域表达了 24 位的尾数。比如对于单精度数而言,二进制的 1001.101(对应于十进制的 9.625)可以表达为 1.001101 × 23,所以实际保存在尾数域中的值为 00110100000000000000000,即去掉小数点左侧的 1,并用 0 在右侧补齐。
4.2.3、双精度浮点数
双精度浮点数是用64bit来存储一个实数,它的位域如下:
- 符号位
最高位作为符号位,1---负数,0---整数 - 指数域
紧接着符号位的11位存储指数部分。11 位的指数为可以表达 0 到 1023之间的 1023 个指数值。 - 尾数域(有效数字)
剩下的52位用来保存尾数。