2.1.1 long不够长
数据是计算机加工处理的对象,为了存储和处理的需要,将数据分为不同的类型,编译程序为不同的类型分配不同大小的存储空间(存储单元的字节数),并且对各种数据类型规定了该类型能进行的运算(运算符集合),任何类型数据的值均被限制在一定的范围内,称为数据类型的值域(取值范围)。当一个变量的数据类型确定后,该变量的取值范围随之确定了。在程序运行时,如果该变量的实际值超出了其数据类型的取值范围时,则会“溢出”。数据“溢出”虽然不会导致编译错误,但是其运算结果不再正确。
先来看一个简单的例子。假设程序2-1是针对例1-1的求解算法,可以验证,对于问题实例“1+1”,程序2-1能运行出正确结果。但是对于实例“1000+1000”,程序2-1则不能得到正确结果。如果把程序2-1中的数据类型char换为int,该程序对例1-1中的所有问题实例能得到正确的解。
程序2-1 正整数求和问题程序-char
如果把例1-1稍作修改,就是把加数a与b的取值范围放宽至1≤a,b≤10200,即a和b可能是非常非常大的正整数,得到如下新问题。
【例2-1】 超大正整数求和问题
问题描述:计算正整数a与b的和c。
输入:正整数a和b(1≤a,b≤10200)。
输出:和c。
众所周知,long是C/C++中长整型数据类型,那么,如果把程序2-1中变量的数据类型由char型改为long型,是否就可以了?答案是否定的。因为long数据类型也不足够表达那么大的整数。
1.数据类型的值域
C/C++有4种基本数据类型:字符、整型、单精度实型、双精度实型。尽管这几种数据类型的长度和范围随处理器的类型与C/C++编译程序的实现而存在差异。一般地,整数与CPU的字长相等,一个字符通常占用1字节,浮点数据类型的确切格式则根据实现而定。基本数据类型除了可以独立使用,它们的前面可以有各种修饰符。修饰符用来改变基本类型的意义,以便更准确地适应各种情况的需求。修饰符如下:signed,有符号;unsigned,无符号;long,长型符;short,短型符。
对于多数微机,表2-1给出了C/C++ ANSI标准中常用数据类型的长度和取值范围。为了表达更大的整数,很多系统中还定义了64位的long long类型。
注:表中的长度和范围的取值是假定CPU的字长为16 位。因为整数的默认定义是有符号数,所以signed是多余的,但仍允许使用。某些实现允许将unsigned用于浮点型,
表2-1 ANSI标准中的数据类型及其取值范围
如unsigned double。但这种用法降低了程序的可移植性,故一般建议不采用。
为了使用方便,C编译程序允许使用整型的简写形式:short int简写为short;long int简写为long;unsigned short int简写为unsigned short;unsigned int简写为unsigned;unsigned long int简写为unsigned long。
2.大整数相加算法
从C/C++的数据类型规范可知,unsigned long型整数变量的取值范围为[0, 4294967296]。显然,它还不够长,不足以表达例2-1中的加数以及它们之和,因此需要设计自己的数据结构和算法求解大整数的加法。
首先,用一个200位的unsigned char数组保存最长为200位的正整数。因为任何C/C++固有类型的变量无法保存一个200位十进制整数,最直观的想法是把十进制整数按照数位保存在一个字符数组(记为cArray)中,让cArray[0]保存个位数,cArray[1]保存十位数,cArray[2]保存百位数,以此类推,直到最高位。
其次,实现按位的大整数加法。方法很简单,就是模拟小学生列竖式做加法,从个位数开始逐位相加,如果两个加数的相应数位之和小于10,则把该值置于和数的相应数位上;如果两个加数的相应数位之和大于等于10,则把该值与10之差置于和数的相应数位上,同时高位进1。注意,两个加数的和有可能超过200位而变成201位,所以存储和数的数组必须至少为201位。实际编程实现时,可以把表示大整数的数组稍微开大一点,避免产生越界错误。
按照上面的方法,可以得到程序2-2所示的大整数相加算法。
程序2-2 大整数相加
总之,在问题求解过程中,每个变量的取值范围都是编程者需要认真考虑的问题,一旦在程序执行过程中变量的实际值超出了其类型的取值范围,就会产生不正确的结果。