购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.6 用同一份代码来实现求余及求商

由于求商与求余函数的大部分代码都是相同的,因此,我们应该将其合并到同一个函数中,使得该函数既可以返回商,又可以返回余数。这个函数的复杂度与合并之前的那两个函数是一样的。C++11可以通过{x,y}这样的写法来初始化一份列表,以便构建此函数所需返回的那一对值:

实际上,对于任何两个相互配套的求商及求余函数来说,它们彼此所执行的操作几乎都是相同的。

编程原则:有用的返回值原则(The Law of Useful Return)

上面这个quotient_remainder函数体现了一条重要的编程原则,这就是有用的返回值原则:

如果得到了有用的结果,那就别把它扔掉,而是应该将其返回给调用者。

这样做可以令调用者“顺便”完成其他一些运算(例如像quotient_remainder函数那样),或是把数据存起来,以供将来调用该函数的时候使用。

然而并不是所有的地方都遵循这条原则,比方说,尽管当前很多处理器都能通过同一条指令来算出商和余数,但是起初的C语言及C++语言却依然用了两个不同的操作符来表示除法及求余运算,这导致程序员无法通过同一次操作获取这两项结果。后来,C++语言用std::div()函数解决了这个问题。

无论是直尺与圆规,还是当前计算机中的CPU,大部分计算工具都能够轻松地执行half(平分、减半)运算,比方说,在计算机上面,它实际上只相当于右移一位而已。但如果你所使用的计算工具并不支持这种运算,那可以考虑采用另外一种巧妙的方式来计算余数,这种方式是由罗伯特·弗洛伊德(Robert Floyd)与高德纳(Donald Knuth)提出的,它不需要进行平分,而是基于一种与斐波那契数列(Fibonacci sequence)类似的思路(这个数列也是由比萨的列奥那多所发现的,我们将在第7章中深入讨论)。与我们早前所用的算法相比,这种算法所用的下一个数字并不是前一个数的两倍,而是前两个数之和

该算法的第一个循环,相当于上一个算法计算largest_doubling的那个部分,而第二个循环,则对应于上一个算法中同half(平分)有关的那些运算,只不过这次我们不做平分,而是通过减法,把b的值还原成“斐波那契数列”中的前一个数。每次执行循环时,我们都借助临时变量,通过当前这一轮执行前的c与b之差,来求出下一轮的b值,同时把当前这一轮执行前的b值用作下一轮的c值。

习题4.6 在4.5节中,我们曾经以45与6为例演示了求余算法的执行过程。现在仍以这两个数为例来演示remainder_fibonacci算法的执行过程。

习题4.7 设计一个求商所用的quotient_fibonacci算法,以及一个能够同时求出商与余数的quotient_remainder_fibonacci算法。

把高效的求余函数实现出来之后,我们回到最初的那个问题上面,也就是怎样计算两条线段的最大公度量。采用4.5节中的remainder函数,我们可以把欧几里得的最大公度量算法改写成:

由于我们允许remainder函数返回0,因此不再需要像原来那样,把a与b相等视为主循环的终结条件,而是应该在b等于0(也就是前一次循环所产生的余数为0)时结束循环。

接下来的几章将试着在不改变算法结构的前提下,把它运用到不同类型的数据上面。尽管该算法最初是仿照着尺规作图搭建起来的,但我们在实现它的时候,却应该从数字化的计算机程序这一角度来思考。比方说,我们可以把该算法运用在整数(integer)类型的数据上面,使其变为两个整数之间的最大公约数(greatest common divisor,GCD)算法。此时的函数应该写成:

这个函数的代码与gcm_remainder函数类似,只不过它把line_segment类型改成了integer类型,并改用模除运算符(modulus operator,%)来计算余数。由于计算机本身已经可以通过相应的指令来计算两个整数相除的余数(上述C++代码中的模除运算符就会调用这种指令),因此,我们最好是直接使用这些指令,而不必再像原来那样依赖翻倍与减半等方式进行运算。 1XD8RM9z2VnbebrPmv232d55GPtkVSk8/BOwZ5b9uaGkNBqmxl2cDO0JOuv54+73

点击中间区域
呼出菜单
上一章
目录
下一章
×