2.7 数据类型与算法

数据类型问题之所以重要,是因为它必须反映问题中数据的特性。不恰当地选择数据类型,可以说在还没编写程序的时候,程序就已经必然会出错了。下面的例题(其中几个代码有问题),提示我们数据类型的重要性。

例题:求1÷19=?要求精确到小数点后20位。

2.7.1 错误的数据类型

1.第一种解法

程序代码2-11

2.7 数据类型与算法 - 图1

由于在C语言中,int类型量除以int类型量的值是个整形量,所以结果只会是:

2.7 数据类型与算法 - 图2

如果把"%d"写成%lf就更荒唐,试图用浮点量的格式输出一个占4个Byte的int类型量,属于概念错误。那么再把1和19写成double类型怎么样?

2.第二种方法

程序代码2-12

2.7 数据类型与算法 - 图3

和第一种方法相比有所改进。首先把1/19改写成了1./19.,这是两个double类型数据的运算,结果也为double类型。又在输出转换格式中规定了输出30个字符,小数点后20位(%30.20f)。表面上看似乎可以满足题目要求。但有一点漏算,那就是double类型的长度有限(8个字节),因此其精度也必然是有限的,通常情况下有效位数折合成十进制只是15位左右。因此第二种方法的输出是:

2.7 数据类型与算法 - 图4

如果打开“计算器”验算一下(或者用纸笔计算一下),就会发现这个结果是错误的。

用纸笔可以得到的结果在计算机中居然无法轻易得到。这个原因非常简单,在纸上演算时是没有那么多限制的(只要纸的大小够就可以一直写下去)。但计算机处理的是固定长度的二进制数,结果必然是有一定限制的。

2.7.2 所谓算法

解决的办法是分析一下我们纸笔运算的过程。如图2-12所示,在计算除法的一开始,首先在纸上写出被除数1和除数19(对应地在代码中也可以写出)。小数点前的0是1除以19得到的(代码中对应1/19)。求余数1对应着代码中的1%19。在余数1后面添0在代码中意味着将这个余数乘以10,这个数除以19将得到0,依此类推……,用程序计算所不同的是在计算过程中只需要描述计算的步骤,而不需要自己计算。下面是相应的代码。

2.7 数据类型与算法 - 图5

图2-12 除法的运算过程

程序代码2-13

2.7 数据类型与算法 - 图6

屏幕输出如下所示:

2.7 数据类型与算法 - 图7

为了不占用更多的篇幅,此处只计算了小数点后面4位。

从这个例子中可以感悟到所谓的算法,其本质就是对计算步骤的描述。用程序设计语言写出的算法,就是源程序。对计算步骤的描述和进行计算本身不同,能够进行计算却不会描述计算步骤,虽然听起来似乎很奇怪,但的确是初学者的一个常见问题。

2.7.3 一个技巧

为了节约篇幅,程序代码2-13只计算了小数点后面4位的结果。若计算小数点第4位后面的各位,后面的代码完全可以通过“复制”、“粘贴”实现。这样做的结果是代码显得有些臃长,但在现在这个学习阶段,这样做无可非议。

不过若是想把代码改得更好些,还是有办法的。

前面提到过,#define预处理命令的含义大体相当于文字处理软件中的“查找”与“替换”,可以使用这个命令为常量命名。实际上这条预处理命令的一般用法是:

2.7 数据类型与算法 - 图8

它的作用是把这条命令后面代码中出现的标识符替换为替换表。这样就可以考虑把代码中的:

2.7 数据类型与算法 - 图9

这样几条语句用一个简单的标识符替代。

然而还有另一个问题需要解决,那就是#define预处理命令一般只能写成一行。这个问题可以用C代码中的续行标志“\”解决。改进后的代码如下:

程序代码2-14

2.7 数据类型与算法 - 图10

屏幕输出如图2-13所示。

2.7 数据类型与算法 - 图11

图2-13 使用预处理命令后的输出

2.7.4 更高效率的写法

有句广告语叫做:“没有最好,只有更好。”这句话用于形容编程再恰当不过了。实际上前一小节中的代码还可以进一步简化。

原来的代码是逐位计算商的,这和人的运算过程是相同的。然而计算机的计算是并行处理的,也就是说,对于计算机来说,2/1与21/11的计算量是相同的。人类之所以逐位计算是因为人类的大脑的“内存”太有限了。要写好代码就必须同时发挥人脑与电脑各自的长处。

由于一个正整数对19求余得到的余数不会超过19,因此这个余数乘以108得到的结果也不超过1900000000(十六进制的713FB300),这个数值用4个字节是足够存储的,因此我们可以一次计算8位。每次计算8位,这样只需要4行计算商的代码就可以得到小数点后面24位精度的商。

程序代码2-15

2.7 数据类型与算法 - 图12

屏幕输出如下所示

2.7 数据类型与算法 - 图13

然而,且慢!如果足够细心的话,会发现上面的结果是错误的。而细心,绝对是编程必备的一个品质。

这个错误产生的原因是%d格式输出时是按照数值的实际长度输出的。比如12/10用%d输出只会输出一个1而不会再输出别的。如果希望输出是两位在d前面应该再写个2。然而12/10用%2d的输出是“1”,也就是说不会输出1前面的0,如果希望输出为“02”,在%与2d之间应再写个0——%02d。

因此应该把代码中的后三个%d改成%08d。格式%08d表明的含义是输出8位,数值不足8位时前面补0(所谓的前导0)。

对于除数较大的情形,这段程序依然成立,不过要注意的是可能无法一次计算8位,这时需要同时把100000000改的小些,其中的道理请自己研究。并请自己改正程序代码2-15中的错误。

如果编译器支持C99,可以将“bcs”、“ys”两个变量定义为long long类型,代码将更为高效简洁。