附录一
1、我们日常使用的数字进制是十进制,也就是“逢10进1”,就是我们数数,从0数到9,到了10就又变化了,又是从0开始。一样的,二进制顾名思义就是“逢2进1”,从0数数,0、1、10、11、100、101...以此类推。我们可能比较常用的还有十六进制数,比如说我们在用Photoshop等图片编辑软件或者其他用到颜色编辑的工具时,时常会看到类似于“#12d54f”之类的表示方法,其实这六位数值就是对应了我们“三原色”RGB,也就是Red(红)、Green(绿)、Blue(蓝),每两位代表一种原色,使用的就是十六进制,就是使用数字0到9,还有字母a到f,两位数值正好表示0到255。常用像“#0000ff”表示蓝色,“#ffffff”表示白色,“#000000”表示黑色。
我们怎么看懂二进制的大小呢?换句话说,二进制怎么转化成十进制来理解呢?举个例子,比如说有一个8位的二进制数“01011011”如下,转换为十进制的,应该怎么转化?二进制数的每一位数都是2的几次幂,就像我们的十进制一样,0是10的0次幂(100),10是10的1次幂(101),100是10的2次幂(102)。上述“01011011”对应每个位置的幂数如下:二进制数转化十进制就是每个位所代表的数值和每个位的数值相乘后相加,也就是:(27 * 0) + (26 * 1) + (25 * 0) + (24 * 1) + (23 * 1) + (22 * 0) + (21 * 1) + (20 * 1)因为整数乘0等于0,乘1等于本身,那么上述计算可简化为:26 + 24 + 23 + 21 + 20 = 64 + 16 + 8 + 2 + 1 = 91也就是说,我们只需要计算数字为1的位所代表十进制数的和就可以了。那我们知道了二进制转十进制,那十进制如何转二进制呢?我们可以使用求余的方法计算,比如刚才的91,如下图:由下往上将余数组合起来就是“1011011”,前面补0凑齐8位就是01011011。2、二进制的运算规则。十进制的计算规则我们都很清楚了,那么二进制又是怎么计算的呢?其实是有规律的,因为只有0、1两个数,规则就简单很多:加法:0 + 0 = 0, 0 + 1 = 1, 1 + 0 = 1, 1 + 1 = 10(进1位)减法:0 - 0 = 0, 1 - 0 = 1, 1 - 1 = 0, 0 - 1 = 1(借1位)乘法:0 × 0 = 0, 1 × 0 = 0, 0 × 1 = 0, 1 × 1 = 1除法:0 ÷ 1 = 0, 1 ÷ 1 = 1(0不能为除数)看起来很多,其实细看一下,跟十进制是一样的,只不过是“逢2进1”的区别,我们可以举例看一下,比如使用1001和0101的四则运算如下图所示:二进制的运算,一般比较少用到,更为常用的是与、或、非、异或的运算,规则更为简单:与:1 & 1 = 1, 1 & 0 = 0, 0 & 1 = 0, 0 & 0 =0(其实就是0和任意数字做与运算都为0,与的符号为&)或:1 | 1 = 1, 1 | 0 = 1, 0 | 1 = 1, 0 | 0 = 0(1和任意数字做或运算都为1,或的符号为|)非:~1 = 0, ~0 = 1(其实就是取反,反过来了,非的符号为~)异或:1 ^ 1 = 0,1 ^ 0 = 1, 0 ^ 1 = 1, 0 ^ 0 = 0(相同的为0, 不同的为1,异或的运算符为^)3、我们学了2.1节之后就知道数值有整数和浮点数之分,整数的存储我们比较好理解,就是通过二进制的存储方式来存储十进制。但是如果有负数我们应该怎么存储呢?所以整数可以分为有符号数和无符号数。有符号数:计算机将存储的最高位用来作为符号位,最高位为0,表示正数,最高位为1,表示负数。无符号数:将全部位数用来表示数值,没有符号位,不能表示负数。我们知道了计算机存储整数的方法,那么计算机做整数运算的话该怎么计算呢?举个例子,如果计算1 - 1的话,按照我们已知的计算方法应该是如下计算:1 - 1 = 1 + (-1) = (00000001) + (10000001) = 10000010 = -2 ≠ 0这显然是不正确的。为了解决这个问题,整数又分了三种编码方式:原码、反码、补码。原码:在数值前面增加了一位符号位(即最高位为符号位),该位为0表示正数,该位为1表示负数,其余位表示数值的大小。反码:正数的反码与其原码相同。负数的反码是对其原码逐位取反,但符号位除外。补码:正数的补码与其原码相同,负数的补码就是对该负数的反码加1。0的补码为0。【从定义可以看出,正数的补码,反码,原码相同。0的补码就是本身。那么负数的原码和补码如何转换呢?已知一个负数求补码方法:绝对值原码按位求反加1。已知负数补码求负数方法:符号位不变,其他位按位求反加1。】反码能解决上述问题吗?我们再算一下:1的反码为000000001,-1的反码为11111110,计算如下:1 - 1 = 1 + (-1) = (00000001) + (11111110) = 11111111 = -01 - 2 = 1 + (-2) = (00000001) + (11111101) = 11111110 = -1结果都是正确的,但是又有一个问题,0是没有+0和-0的区别的。我们在看一下补码的计算:1 - 1 = 1 + (-1) = 00000001 + 11111111 = 00000000 = 01 - 2 = 1 + (-2) = 00000001 + 11111110 = 11111111 = -1结果都是正确的,显然,计算机使用补码来存储和计算是比较方便的。在Python里面,整数是无限大的,理论上内存多大,整数就可以多大,所以int类型的数据没有范围,但是如果在C语言中,int类型则有一些受限,比如是在32位的电脑中,int占32位,取值范围为-2147483648~2147483647(-2^32~2^32-1)。如果超出范围怎么办,那就有“溢出”的风险,比如说4位范围的数,最大已经到了1111,如果这个时候再加1,则会变成10000了,而程序只能读4位,该数值就变成0,我们称为溢出。C语言更为关心计算机底层,所以编写C语言则需要格外小心。C语言还有long类型和long long类型,来表示更大的数值范围。在python2还有long类型,在Python3里面就没有了。我们可以更关心我们的编程逻辑而不用担心更加底层的问题4、知道了整数的存储和计算之后我们再聊一下小数,其实不是所有小数都叫做浮点数,浮点数是一种表示数字的标准,整数也可以用浮点数的格式来存储。小数的表示方法除了浮点数还有定点数,区别就是小数点能不能“浮动”,浮点数的小数点在逻辑上是不固定的。浮点数可以用一个表达式来表示:V = (-1)s × M × 2E我们来逐个解释一下:符号(sign) :1个bit表示,当s=0,V为正数;当s=1,V为负数。阶码(exponent) :E的作用是对浮点数加权,用于存储科学计数法中的指数数据,并且采用移位存储。尾数(significand) :M是一个二进制小数,因为是二进制,所以科学计数法中这个值范围是:1≤M<2。(和十进制中范围为1~10一样)一般在编程语言(如C语言)中浮点类型一般是采用单精度(float)和双精度(double)来存储,float数据占用32bit,double数据占64bit。在python中是使用了双精度来存储float,没有double。
十进制的科学计数法我们比较熟悉了,比如说:123.5用十进制的科学计数法可以表示为1.235×102。但是计算机用的是二进制,123.5换算成二进制就是1111011.1,用二进制的科学计数法表示就是1.1110111×26。
这样看,我们在浮点数的内存存储,只需要存3个部分,也就是符号、指数和尾数。符号位我们知道1bit就足够了,指数和尾数多大比较合适?IEEE(电气和电子工程师协会)就制定了一些标准,单精度float 遵循的是IEEE R32.24,双精度遵循的是IEEE R64.53。在内存上的存储如下图:
对于尾数M,我们可以看到取值范围是1≤M<2,总是可以表示成1.xxxxx的形式,前面的1是固定的,所以IEEE就另外规定了,在保存M时,前面的1隐藏起来,要读取时再添加回去,这样就可以节省1位,就相当于有24bit的尾数位。224=16777216,因为107 < 16777216 < 108,所以单精度浮点数十进制的有效位数是7位。
而我们计算也能得知,双精度十进制的有效范围是15位。我们在python中验证一下,python的float有效精度是有16位的。如下图:
怎么回事?其实就是一个精度损失的取舍问题,当我们计算机在计算无穷数的时候,能取到17位但17位一般不准确,第16位大概率是准确的,所以就取数到16位。16什么时候会不准呢?如下图,当我们计算小数时是有可能发生最后一位数值出错的问题的。所以仅能保证到15位,但是15位还要依赖于16位的舍入,单精度也是一样的道理。这个问题我们要知道,但是不深究了,细说起来就很多了,有兴趣的同学可以上网查一下浮点数精度损失的相关介绍。
说完尾数M,我们再说一下指数E。我们上面只讲到正整数的指数,但科学计数法中,指数是有负数的,而E是一个无符号数,因为它没有符号位,于是IEEE又规定了,在保存真实E值时,E要加一个中间值。单精度情况下,E加127,双精度情况下,E加1023,这样负数也能存进来了。另外,当E取值不同时,对整数值的计算方法也不一样,有下面三种情况。① 当E不全为0,也不全为1时:表示规格化形式的数字,按正常计算。此时E减去中间值得到真实值,M的整数部分按上面说的取值为1。② 当E全为0时:表示非规格化形式的数字,主要是0或者非常接近于0的数。此时E减去中间值得到真实值,M的整数部分取0。③ 当E全为1时:表示特殊值。如果M全为0,表示±无穷大(正负取决于符号s),如果M不全为0,表示这不是一个数(NaN)。