前言
一个朋友问我为什么浮点数的会丢失精度,于是产生了下面一段对话
朋友:为什么浮点数会丢失精度呢?
我:我们知道浮点数是用尾数、指数、和底数表示,计算机内是使用2进制,底数是2,而指数用移码表示……
朋友:等等,移码?为什么不用原码、反码、或者补码表示呢?
我:这是由于……
朋友:(打断我),算了,你还是先给我讲讲为什么要有这么多编码吧 我:balabala…………
这就是我写这两篇博客的原因,我一直以为我对编码了如指掌,但是当我试图给别人讲清楚时却发现困难重重,于是我将自己的认识重新梳理了一下,写了关于编码的上下两篇博客,上篇讲我们为什么要编码以及为什么需要这么多类型的编码,下篇讲什么是浮点数以及浮点数为什么会丢失精度,顺便对网上的一些结论做个解析。
正文
我们在大学计算机课上就学习了原码、反码、补码,背了一系列的规则来记忆他们之间如何转换,然而为什么这样转换却不甚了解,本节就来介绍为什么我们要为数字编码,原码、反码、补码的产生背景是什么,以及为什么是这样的关系。
在这之前,我们必须明确一点,码和数是完全不同的两个概念,码是用来表示事物的记号,它可以用数字、字母、特殊的符号或它们之间的组合来表示,码没有大小,没有正负,比如我们每个人一出生就有了身份证号,身份证号就是我们的编码。同理数字也可以有编码,我们可以使用a表示1,也可以用0001表示1,所以这里先明确一点,原码、反码、补码都是码,他们所表示的才是数字,尽管正数的原码和正数的二进制本身看起来一样,但这并不意味着他们是等同的。为了更为形象的解释计算机内编码和数字的关系,我举这样一个例子,我们一堆写着数字的卡片乱序排在一起,比如第2张卡片上写着数字9,第3张卡片上写着数字8,那么2和3就分别是8和9的编码。
明白了数和码的区别,我们再来探讨为什么需要编码,我们知道计算机只能表示数字1和0,开表示1,关表示0;假设我们的计算机只有4个开关(4位),这四个开关可以表示0000、0001、0010、0011……1011、1111共计2^4=16个状态(下文称为机器码)。我们如果直接用这些机器码表示数字,将0000-1111看作是二进制数,那么换算为10进制即为0~15,其缺点显而易见,这并不能表示负数。
原码
为了解决负数问题,编码闪亮登场,我的为每个数字编码,而将计算机机器码的左边数第一位定义为符号位,0表示正数,1表示负数,这种编码形式我们称之为原码。计算机的状态码不再直接表示数字,而表示这数字的原码,机器码0000、1111即为原码,分别表示数字 0和-7。4位计算机表示的原码和数字对应关系如下:
原码的出现解决了计算机不能表示负数的问题,然而却存在如下问题:1)有 0和-0,原本可以表示16个数字但是现在只能表示15个;2)编码是人类发明的,计算并不知道编码的存在。因此计算机所有的计算依然是对编码的计算,我们期望两个数的编码相加所得到的编码所表示的数字恰好等于这两个数字的和,然而原码却并不如此:1 (-1)= 0,但他们的原码0001 1001=1010,而1010表示-2,这显然不符合我们的预期。
反码
现在我们换个角度看上面的问题,即一个正数的编码加上他的相反数的编码等于0的编码,再抽象一些,任何一个数的编码加上他相反数的编码等于一个固定的编码,这个固定的编码表示为0;这就简单了,我们自然而然的想到0000 1111=1111,0001 1110=1111……0111 1000=1111,即0000~1111,我们从左到右的8个数字分表表示0~7,而从右向左的8个数字分别表示0~7的相反数,这样互为相反数的两个数字的编码相加等于1111,而编码1111恰好表示为-0,我们称这种编码为反码。4位计算机表示的反码和数字对应关系如下:
现在我们再来解释网上的负数的反码计算规则:符号位不变,其他位取反。注意,4位情况下负数的反码的计算方法应该是1111-这个负数的相反数的编码,只是在二进制情况下计算的结果看起来恰好是符号位不变、其他位取反,很多人只是把这条转换规则记得很熟,但是并不知道为什么这样能得出正确的结果。
反码的出现解决了计算机不能通过编码计算正负数相加的情况,却依然没解决 0和-0同时存在的问题。
补码
16个机器码却因为正负0的问题只能表示15个数字,在寸土寸金的内存里,浪费就是犯罪,为了去掉这个多出来的0,补码出现了。对照反码的对应关系图,我们发现把1000~1111所表示的数字-7~-0都减去1,即1000~1111表示-8-~-1不就解决问题了吗,现在我们再来看一下:0001 1111=10000,0010 1110=10000……0111 1001=10000,而计算机只有四位,存储时10000的最高位会被丢弃掉,在计算机中就会被存储为0000,恰好就是0的编码。4位计算机表示的补码和数字的对应关系如下:
现在再来解释课本上的一 条负数的补码计算规则,课本上都是以8位为例,负数的补码等于负数的反码加1,10000000时比较特殊,表示最小的负数-128。其实明白了补码要解决的问题我们就可以知道这个-128根本不需要特殊记忆,因为10000000表示-128是自然而然的事情。
总结:原码的出现是为了解决负数的存储问题,反码的出现是为了解决计算机负数的计算问题,补码的出现则是为了解决正负0的问题。网上关于原码、反码、补码的计算有各式各样的口诀能够让我们“莫名其妙”的计算出正确的结果,然而当我们知道原码、反码以及补码的产生背景以及要解决的问题时,一切都变得自然而然起来。