编码精粹(Essential Code)——总论
缘起
编码是一个自学习编程以来就困扰了我很久的东西,它就像茅坑里的石头一样又臭又硬,让人似懂非懂。苦苦思索之后,有一天我突然意识到编码其实也就这么回事儿,不知道这算不算是顿悟。又过了很长时间之后,也就是现在,我决定将我的理解写出来,帮助还在痛苦挣扎的同学脱离苦海。
在写这一系列文章的时候,我参考过的资料有《Computer System:A Programmer's Perspective》,中译名为《深入理解计算机系统》。这本书很好,但是我认为它对编码这一部分的讲解还是有些难以让初学者接受,一上来就摆几个公式,我估计很多同学看完就直接晕菜了。《Code: The Hiden Language of Computer Hardware andSoftware》,中译名《编码》(新版)或者《编码的奥秘》(旧版)也给了我很大的启发。刚一到手,我就被书里的内容深深吸引了,第一次体会到了技术书(虽然科普更多一些)也能这么好读。《编码》主要以直观形象的理解为主,讲得非常好,不过深度欠缺了点儿。另外还有《Introduction to Computer Systems: From Bits and Gates to C and Beyond》,中译名《计算机系统概论》。这本书也非常好,不过好像在国内名气不是很大。其他一些书籍和资料就不一一列举了。如果你想要我推荐书籍列表的话,直接在文章后面留言就可以了。
正题
各种编码(Code)及其相关理论在计算机系统中处于基础而重要的地位。不过令人尴尬的是,大多数计算机教材和参考资料在涉及到编码的内容时,不是一带而过语焉不详,就是单纯罗列一些概念。学生(包括曾经的我),学完之后往往处于一种懵懵懂懂的状态。浅显的了解对于初学编程的人来说是足够的,但是随着学习的深入,跟编码有关的问题会时不时地出来敲打你一下,让你有一种惶惶然大祸临头的感觉。
有的同学可能会很不服气。编码啊?不就是什么二进制十进制嘛,太简单了!我会各种进制转换,还知道怎么计算补码……呃,当初我也是这么想的,但是慢慢地,我发现自己有些too young,too naive!且看,在大多数32位C语言实现中,0x7FFF FFFF
的类型是什么?0xFFFF FFFF
的类型是什么?-0xFFFF FFFF
的类型又是什么?晕了?别着急,还有呢。我估计你已经把计算补码的方法(求反加一),像乘法口诀一样背得滚瓜烂熟了。但是,这么做背后的原理是什么?C语言中表达式-1<0U
的结果是true还是false?无符号数和有符号数转换规则是怎样的?类似的问题我还能问出一大堆来。如果这些正是你困惑的地方,那么你来对了。如果这些问题你都了解,我相信你也会在这里有所收获,至少可以把自己以前的理解整理成为体系。
既然要讨论编码的问题,那我们首先就要界定一下这个词的含义,下面是Microsoft Computer Dictionary 5th Edition中的解释:
Code:
- Program instructions. Source code consists of human-readable statements written by a programmer in a programming language. Machine code consists of numerical instructions that the computer can recognize and execute and that were converted from source code. See also data, program.
- A system of symbols used to convert information from one form to another. A code for converting information in order to conceal it is often called a cipher.
- One of a set of symbols used to represent information.
上面提到了Code作为名词时的三种含义:一、代码,也就是通常我们所说的源代码;二、一种用来转换信息表现形式的符号系统,特别地,当这种符号系统用于保密目的时为密码;三、表示信息的一组符号,比如拉丁字母可以用来表示英文信息。当我在这个系列的文章中提到编码时,主要指的是第二种含义,并且不考虑其作为密码的用途。换句话说我们讨论的是,在计算机系统中以一组符号和相应的规则来转换信息表示形式的方法。既然是在计算机系统中,那转换后的形式自然是二进制数据(别跟我打岔,说什么苏联的Сетунь是三进制blabla的),而根据源信息的不同,我们会分别讨论整数、实数以及文本信息的编码。
开讲之前,我觉得有必要再强调几个问题:
-
我们讨论的是编码的问题。也就是说,将源信息转换为其在计算机中表示形式的问题。补码并不天生就是负数,它只是我们用来表示负数的一种编码方案而已。8位二进制数码1000 0000也不天生就是正整数128,,我们只是选取这一组二进制位来表示128,如果我们高兴的话我还可以认为它是-128(8位补码),或者其他任何一种数值比如说3。熟悉数学的同学应该已经体味出来了,我们这里所说的编码其实就是映射。补码是一种映射,反码也是一种映射,我们后面要讨论的各种编码都是映射。计算机编码所要解决的问题就是,选取恰当的映射将要表示的源信息映射为一组二进制位。
-
各种编码往往只能表示其源信息的一部分,也就是说对于我们所讨论的编码其定义域往往只是我们所要表示的源信息的一个子集。举个例子,以8位二进制补码来表示整数,其范围只能是-128~127。整型数据的溢出(overflow)即由此而来:当运算结果超出整型数据的表示范围的时候便会发生溢出。浮点数也是如此,浮点数本来用来编码数学中的实数,但实数是连续的,有无限多个,而浮点数是离散的,远远不能涵盖整个实数域。IEEE 754所定义的单精度浮点数为32位,最多可以表示2^32个不同的值,再刨除NaN等几个特殊值,这个数字还要更小。所以对于浮点数,如果某个实数值不能被精确表示,那真是再正常不过了,就算其在浮点数能表示的最大值和最小值之间也是如此。文本信息也有一样的问题,否则的话就不会有从ASCII到各种奇葩的DBCS(双字节字符集),最后到Unicode的痛苦演进了。那你问我说有没有一种编码方案能编码它所需要表示的所有对象啊?有啊,当然有,而且编码空间还有富余呢。比如说BCD码(Binary Coded Decimal),用4个二进制位编码09这10个十进制数码,4个二进制位能表示16个不同的码字,09却只有10个数码,所以还有6个码字的空闲呢,于是BCD码有所谓的8421码、循环码、余三码等等。不过这种编码不是本系列文章的主要研究对象。
-
对编码的论述以C和C++中的实现为蓝本。C和C++对于我们需要介绍的整数、实数、文本信息的编码都有完整的实现方案,而且这两种语言有比较接近硬件,以C和C++为基础讲解的话也更符合我们讲解计算机基础的知识的目的。如果采用Java的话,Java里面没有无符号数……
检测
- 编码是什么?它要解决什么问题?
碎碎念
表达能力欠缺,老板说过不是一次两次了……