前言
最近遇到一道求阶乘的题目,原以为极其简单,但是阶乘的结果超过了范围最大的基本数据类型的范围,于是就着手研究大数运算(large number computing),本篇先介绍大数加法。
原理
大数运算的原理其实就是模拟人工计算(注记:再考虑是否有其他算法。注记日期:2017.3.19),人工加法计算步骤如下:
1.将两个操作数(operand)位数对齐。
2.从最低位开始,计算两个操作数每位的总和再加上进位数(第一位数为0)的结果,保留其个位数。
3.若上述的结果大于10,进位数置为1,否则为0。(每位数相加的结果不会超过 9 + 9 = 18 , 因此进位数只有1和0)
4.重复上述步骤2、3,直到计算完两个操作数其中那个位数小的最高位。
知晓了步骤,就需要考虑设计储存操作数的数据结构。首先使用列表是毋庸置疑的,而顺序结构和链表结构其实都行,这里为里减少代码量和说明问题,暂且使用顺序结构。
综上,给出操作数的结构体如下:
1 typedef struct operand2 {3 int data[100];4 int digit; // 记录该数位数5 } OPERAND;
注意,这里给出最大位数为100,下面给出的代码中没有考虑溢出问题,请读者自行解决。
实现
首先,思考上述步骤1的对齐问题,可以定义一个偏移量(offset)来存储两个操作数的位数之差,之后遍历的时候可以给位数少的增加偏移量来达到对齐目的。可是这种做法会增加一定的代码量,因此不妨试着把数字倒着存放,即数组第一个元素存储操作数的最低位(注意前述做法的前提与此相反),这样做就省去了写对齐的代码。
下面给出该出加法函数,其中结果保存到第一个操作数中:
1 void plus(OPERAND* operand1,OPERAND* operand2) 2 { 3 int carry = 0; // 进位标记 4 int i = 0 , j = 0; 5 for(;i < operand1->digit || j < operand2->digit;i++ , j++) 6 { 7 int sum = operand1->data[i] + operand2->data[j] 8 + carry; // 计算该位数总和加上进位数 9 operand1->data[i] = sum % 10; // 更新该位数的数据10 carry = (sum >= 10)? 1 : 0 ; // 判断是否进位11 }12 operand1->digit = (operand1->digit > operand2->digit)?13 operand1->digit : operand2->digit ;14 // 更新位数15 if(carry) // 若最后需要进位16 {17 operand1->data[operand1->digit] = 1;18 operand1->digit += 1;19 }20 }
其中,sum % 10 用于求个位数,还有注意最后别忘了最高位的进位问题。