提到方程,用计算机解方程这个话题是一定会涉及的。我们在前面曾提到过计算机可以用来下棋,当然它还有别的“本事”,比如把一种语言翻译成另外一种,或者演奏乐器,等等。
我们在这里并不打算对下棋或者进行语言翻译等过于复杂的计算机程序进行研究,我们想要分析的只不过是两个极简单的程序,但在这之前,还是要先来简单地谈谈计算机的构造。
我们在第一章曾提到过的,能用一秒钟的时间进行成千上万次运算的装置,是计算机中负责进行计算的部分,它叫作“运算器”。事实上计算机中还包括控制器和存储器(记忆装置),控制器负责整体调控计算机的工作,存储器的作用是存放数据和预定信号。除此而外,计算机中还有专门用于输入新数据和输出计算结果的装置,打印机会帮助计算机将这些计算结果以十进制的形式打印在专门的卡片上。
就像你所知道的那样,我们可以用唱片或录音带录下人的声音,然后用机器反复播放。但这两种录音方式也有不同,用唱片录音是一次性的,一张唱片只能录一次,而用录音带就不一样了。录音机在录制声音时,是借助一种特殊胶带的磁性作用来进行的,录在录音带上的声音,不仅可以反复播放,还可以把录过的内容“抹掉”再录新的。每条磁带都能反复使用,想要“抹掉”旧内容,只要直接录上新内容就可以了。
计算机的记忆装置与上述原理相同。计算机借助电信号、磁信号或机械信号,将数和预定的信号录到专门的磁鼓、磁带或其他装置上,这些数与信号可以随时“读取”,或随时“抹掉”后重新录入。在记忆或读取这些数与信号时,计算机所用的时间只不过是百万分之几秒而已。
存储器内包含着几千个单元,其中的每个单元内又有磁性元件等几十个存贮原件。我们假定二进制的存贮方式为:用数字1表示1个被磁化了的原件,用数字0表示没有被磁化的元件。例如存储器内的每个单元里含有的元件数是25个(即有25个二进制位数),其中第1个元件用来表示数的符号(+或-),第2~15个用来存贮数的整数部分,其余的10个记录小数部分。图11中显示的是存储器中的两个单元,每个单元里都有25个数位,“+”号表示被磁化的元件,“-”号表示没有被磁化的元件。我们首先来观察第1个单元(虚线的作用是将表示符号的数位与其他数位区分开,逗号的作用是将整数与小数部分区分开),这个单元计入的数是二进制的“+1011.01”,也就是十进制的“11.25”。
图11 存储器中的单元
存储器不仅可以用来存贮数,它也可以用来存贮指令,我们所说的“程序”就由指令组成。现在我为你介绍所谓的三地址的计算机指令:为了记入指令,要将存储器的单元分成4段,图11中的第二个单元就被虚线分成了4段,第几段就表示第几道程序,每道程序都以数(编码)的形式被记入存贮单元里。比如:
加——操作Ⅰ
减——操作Ⅱ
乘——操作Ⅲ
……
指令是如何被完成的呢?存贮单元的第1段是操作码;第2、第3段是存贮单元的编号(地址码),为了完成这一程序,应从这两段中抽取数字,第4段是用来存放所得结果的贮存单元的编号(地址码)。比如,图11中第2个单元所计入的数是二进制的“11、11、111、1011”,也就是十进制的“3、3、7、11”,它们代表的指令是:使用第3和第7存贮单元的数字完成第Ⅲ道程序(即乘法程序),将结果存入第11存贮单元。
接下来我们不需要像图11中所显示的那样,将预先设定好的符号记录下来,而是要直接用十进制的形式把数和命令记录下来。比如图11中的第2单元,我们只需记录:乘3711。
现在我们可以对两个最简单的程序进行分析了。首先是第一个:
这个程序的5个存贮单元记录下了计算机上述指令进行工作的过程。
首先指令1:要求计算机把第4单元和第5单元的数相加,将结果存入第4单元(取代第4单元的原存贮数据)。计算机在第4单元记入的数是0+1=1,完成这条指令后,第4、第5单元所存贮的数应该是:
(4)1
(5)1
再来看指令2:要求计算机把第4单元的数与它自身相乘(即算出它的平方),将结果12记到卡片上,箭头的意思是将结果输出。
接下来是指令3:要求计算机将操作转移到第1单元。也就是说,要从指令1开始依次重复执行各条指令。于是计算机开始重复执行指令1。
指令1:把第4单元和第5单元的数相加,将结果存入第4单元,现在第4单元里的数据就变成了1+1=2:
(4)2
(5)1
指令2:把第4单元的数与它自身相乘,将结果22写到卡片上。
指令3:将操作转移到第1单元,也就是再次从指令1开始重复。
指令1:将2+1=3存入第4单元:
(4)3
(5)1
指令2:将3的平方32写到卡片上。
指令3:将操作转移到第1存贮单元。
……
现在我们已经弄清楚了计算机的工作程序:按顺序计算整数的平方,并将结果写到卡片上。在这个过程中,不需要你亲自去用手敲出每个数,计算机会自己按顺序把整数选出来并且逐一计算出它们的平方。只用几秒钟的时间,甚至是在不到1秒钟的时间内,计算机就可以算出从1到10000的所有整数的平方。
不过,在实际的操作中,让计算机计算整数平方的程序比我们所写的这个程序可要复杂多了,而这个“复杂”的部分首先就出现在指令2。要知道,将结果写在卡片上所用的时间要比完成一道程序多好多倍,所以计算结果出现以后并不能及时地被记入卡片,而是要先被“寄存”在存储器中的“空”单元里,然后再被“慢条斯理”地写在卡片上。具体的方法是这样的:第一个结果寄存进第一个空单元,第二个结果寄存进第二个空单元,第三个结果寄存进第三个空单元……而我们所举的例子里所写的程序就没有详细到这个地步,关于这个内容提也没提。
除此而外,我们还有一个内容没有提到,那就是计算机并不能这样持续不停地进行平方运算,原因在于存储器中的存储单元数量有限。也许你会想,既然我们只想要它计算从1到10000的整数的平方,那么等它算完10000的平方立刻把计算机关掉就好了。但你怎么会知道什么时候恰好算到了10000呢?计算机每秒钟能进行几千次运算,我们根本不可能及时地发现它计算到了我们想要的数,并及时打断它。怎么办呢?很简单,在编写程序时就为它设计一套特殊的指令,使它在一定的时候自动关机。比如编写这个程序时,我们就可以加入让机器计算完1到10000的所有整数平方后自动停机的指令。
说好了只分析简单的程序,因此我们就不再涉及更复杂的指令了。计算从1到10000全部整数平方数的完整程序应该是这样的:
指令1、2与前面分析的简单程序区别不大,完成这两条指令,第8、9、10单元的数据变为:
(8)1
(9)1
(10)12
指令3:求计算出第2和第6单元的数据之和,再将结果存入第2单元。第2单元发生如下变化:
这里可以很清晰地看到,指令3完成后,指令2中的一个地址发生了变化,这种现象的原因,我们会在后面进行分析。
指令4:这条指令与前面简单程序中的指令3一样,要进行条件转移。这条指令的具体内容是:如果第8单元的数小于第7单元的数,则转移到第1单元,如果情况正好相反,则进行到下一指令(指令5)。我们可以看到,第8单元的数“1”的确比第7单元的“10000”小,所以转移到第1单元,重复指令1。
指令1完成后:第8单元变为
(8)2
指令2完成后:第2单元变为
即将22的结果存入第11个单元。现在你知道为什么要提前完成指令3了吗?是的,因为第10个单元已经被占用,新计算出的22必须被送入下一单元。
完成指令1、2之后,我们得到了如下数据:
(8)2
(9)1
(10)12
(11)22
指令3完成后,第2单元有了新变化:
这意味着计算机已经做好将计算结果存入下一单元(即第12单元)的准备。你一定已经发现,目前第8单元里的数据仍然小于第7单元里的数据,所以执行指令4的方式仍旧是回到第1单元。
计算机再次完成了指令1和指令2,我们得到的数据为:
(8)3
(9)1
(10)12
(11)22
(12)32
就像你看到的那样,计算机会按照这个程序,不停地将平方数的计算进行下去。等到第8单元中的数变成10000之后,这种计算才会停止。因为计算机按照程序算完了从1到10000全部整数的平方数,这时第8单元里的数已经不再比第7单元里的数小,指令4不会再要求转移至第1单元,而是进行到下一指令,指令5的命令就是“停机”。
下面来看我们要分析的第二个程序,这个程序相对复杂一些:解方程组。当然,这里所给出的仍旧会是一个被简化了的程序,如果你有兴趣,也可以将它完整地写出来,这个方程组是这样的:
解方程组,这并不难:
解出这个方程组,你至少也要花上几十秒的时间,但计算机解几千个这种方程组,最多只需要1秒钟。
假设有下面一些方程组:
其中a、b、c、d、e、f、a'、b'、c'……都是已知的常数。
我们来看看让计算机解这些方程组所用的相应程序:
指令1:将第28单元与第30单元里的数据相乘,将结果存入第20单元,即将ce写入第20单元。
接下来依此执行指令2~6,完成后第20~25单元里的数会变为:
(20)ce
(21)bf
(22)ae
(23)bd
(24)af
(25)cd
指令7:用第20单元里的数减第21单元里的数,将结果存入第20单元,即将ce-bf写入第20单元。
接下来依此执行指令8~9,完成后第20~22单元里的数会变为:
(20)ce-bf
(21)ae-bd
(22)af-cd
指令10和指令11要求完成除法:
并将结果记入卡片。
到此为止,第一个方程组中的未知数就全部解完了。
那么其他指令的作用是什么呢?本程序的第12~19单元是使计算机解第二个方程组的指令,其中指令12~17是要求计算机分别计算第1~6单元里的数与第19单元里的数之和,再将结果存回第1~6单元。指令17完成后,第1~6单元里的数据发生了如下变化:
(1)×34 36 20
(2)×33 37 21
(3)×32 36 22
(4)×33 35 23
(5)×32 37 24
(6)×34 35 25
指令18要求转移到第1单元。
在接下来执行指令的过程中,第1~6单元里的数据会发生什么变化呢?很简单,前两个地址的编码不再是26~31,而是变成了32~37。也就是说,机器反复进行着同样的运算,只是这一次是从第二个方程组各系数所在的第32~37单元选数,而不是存贮着第一个方程组各系数的第26~31单元了。第二个方程组就这样解完了,同样的方法,机器继续解第三个方程组,甚至更多的方程组,直到将要求解出的方程组全部解完为止。
分析上面两个程序的过程让我们清楚地意识到学会正确编写程序的重要性。机器靠“自己”其实是什么都不会做的,它只会完成人交给它的程序。这些程序种类繁多,比如计算平方根的程序、计算对数的程序、计算正弦函数的程序、解高次方程的程序,当然还有我们所提到过的下棋程序、翻译程序等。一个明显的特征是:人要求计算机做的工作越复杂,交给它的相应程序就会越复杂。
计算机还有一个重要的功能,那就是编写程序,但这需要人首先交给它一种“编程程序”,有了这种“编程程序”,计算机就能按照程序中的指令自动编写出相应的程序来。编程工作通常是非常繁重的,这种编程程序的使用,能大大减轻人工编程所要承受的“沉重”负担。(俄.别莱利曼)
有话要说...