有符号数、无符号数、浮点数的理解

初学计算机的时候,我们就被告知,计算机只能理解0和1,任何东西都是由0和1编码组成。本文针对二进制数字是如何组成有意义的数字,涉及到的编码形式进行理解,主要集中在三个概念:有符号数、无符号数、浮点数。虽然这些知识点在实际工作当中真正派上用场的机会并不多,但是对于理解计算机是如何联系真实世界,还是很有意义的。本文通篇都是围绕一个问题进行展开:如何对十进制数字进行编码,转化为计算机能够存储、传输的二进制数字?
一.原码和补码 首先是原码和补码这两个比较基础的概念,原码是一种非常自然二进制编码形式,和十进制非常类似。补码是相对来说比较抽象的概念,它主要是可以解决原码中同时存在+0和-0两种编码的缺陷。
1.原码

原码:是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。例如,对于十进制数字23,它的二进制原码为 10111【即 2^4 + 2^2 + 2^1 + 2^0=23】。
2.补码
补码:N位数字的补码是相对于2^N, 一个原码数字和它的补码数字的和正好是2^N。例如,对于三个bit的数字010, 则它的补码是110, 因为 010+110=1000。它的计算是通过对原码数字取反再加一得到的。
补码还有一个特点就是从原码到补码和从补码到原码的运算过程是一模一样的,举例说明:-1 对应原码 1 0000001,除符号位外,进行取反加1,得到 1 1111111。再进行一次,除符号位外取反加1,得到 1 0000001,得到的刚好就是原码。这个特点对实际运算的简化是非常有利的。
二.无符号数和有符号数 无符号数就是针对非负整数数进行编码得到的二进制数字,编码的方式就是原码方式。因此对任意的非负整数N, 我们都可以用 log2(N)位二进制数字进行表达。 与无符号数相对应,有符号数就是针对一切整数进行编码得到的二进制数字。如何表示负号,一个很自然的方法是采用最高位作为符号位来表达正负。最高位为1代表是负数,为0代表是正数。如果用一个字节8位来存储,则-23就是 10010111 。一个字节所能表示的范围就是 -127 到 127。
但是这种有符号数的表示方式会有一个很明显的问题:1000000 和 0000000 都表示十进制0。为了解决这个问题,就是用原码来表示非负整数,补码来表示负整数。-23就是 11101001。+0 就是 0000000, 原来的 -0 就是 1000000,把它作为补码形式来理解,所对应的十进制数就是 -128。虽然这里实际上是溢出了,但是如果不管它溢出的话,对应的8bit的二进制数跟用补码形式表示的 -128是一模一样的。这种有符号数的表示方法对实际数学运算的好处是非常明显的。下面举一些实际例子来进行说明,这两个例子为了方便说明,都是以一个字节来进行表示,更大范围的数字增加字节进行存储,表示方法以此类推。
1. -128+127
-128就是 10000000,127就是 01111111。按照二进制进行相加,结果是 11111111。【再变回原码,除符号位外取反加1,就是 10000001,对应的十进制数也就是 -1,这个过程只是为了让我知道 11111111 表示的就是 -1,计算机没有必要这样做,对于它来说, -1就是 1111111】。
2. -5+(-6)
-5就是 1 1111011, -6就是 1 1111010。相加的结果是 11110101 【变回原码是10001011,对应 -11】。
三.浮点数 对于实数域来说,还有一个对象就是带小数位的实数,带小数位的实数如何用二进制进行表示?这里就需要用到浮点数的概念了。
浮点数,是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。
浮点数的概念与十进制的科学计数法有点类似,一个十进制数1234可以表示为 1.234 x 10^3,
它可以分为尾数部分1.234和指数部分10^3 。1.234用二进制表示,小数点前数字1用八位二进制来表示的话是 00000001,0.234变为二进制表示的如果是abcd……, 则依然符合 a x 2^(-1) + b x 2^(-2) + c x 2^(-3) + d x 2^(-4) + ……。实际当中采用手算计算尾部二进制的话,方法如下: 0.234 x 2 = 0.468, 取整数部分0作为第一位;0.468 x 2 = 0.936, 取整数部分0作为第二位;0.936 x 2 = 1.872, 取整数部分1作为第三位;0.872 x 2 = 1.744, 取整数部分1作为第四位;0.744 x 2 = 1.488, 0.488 x 2 = 0.976, 0.976 x 2 = 1.952, …… 。合起来就是 0000001.0011101…… , 以小数部分后7位来看,变为十进制数就是 0.2265。如果想进一步精确表示0.234,所需要的二进制位数就越多。大部分时候,二进制表示小数部分都不能得到一个准确表示。所以,才会说计算机中都是用以一定精度来近似表达实数的。
按照前面的例子,1.234表示起来就是 0000001.0011101,这还只是个近似表示。对于这个二进制数,如果直接用计算机进行存储的话依然有很大的问题,比较浪费存储空间。解决方法就是采用浮点数的表示方法。
有符号数、无符号数、浮点数的理解
文章图片

如上图所示,浮点数主要是分为三部分:符号位 指数位 尾数部分。这里浮动是指二进制数小数点的移动,移动的准则是保证第一位为1,由于第一位永远是1,所以可以省略,只剩下位数部分小数点后面的若干位。浮点数主要是有两种形式:单精度32位和双精度64位。
有符号数、无符号数、浮点数的理解
文章图片

对于用32位表示的浮点数来说,指数部分左移为正,右移为负【指数部分在这里没有符号位】,为了保证非负,采用一个默认的偏移量,8位指数位的原码形式最大能表示的是-127,指数位的默认偏移量就设置为127。 对于64位表示的浮点数来说,指数位为11位,默认的偏移量就是 0x03ff。单精度类型所能表达的大致范围就是 2^(-127) 到 2^(128) ,即 -10^(39) 到 10^(39) ; 双精度类型所能表达的大致范围就是 2^(-1023) 到 2^(1024) , 即 -10^(308) 到 10^(308)。
参考文献
[1]: 浮点数 http://cstl-csm.semo.edu/xzhang/Class%20Folder/CS280/Workbook_HTML/FLOATING_tut.htm
【有符号数、无符号数、浮点数的理解】[2]: 百度百科和维基百科 https://en.wikipedia.org/wiki/Two%27s_complement

    推荐阅读